Match-id-a092aad4ac995c34d6dabdda374ca0811de568f5
This commit is contained in:
commit
109db22ba4
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||
*
|
||||
* openGauss is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
*
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
printWidth: 120, //一行120字符数,如果超过会进行换行
|
||||
tabWidth: 2, //tab等2个空格
|
||||
useTabs: false, //用空格缩进行
|
||||
semi: true, //行尾使用分号
|
||||
singleQuote: true, //字符串使用单引号
|
||||
quoteProps: 'as-needed', //仅在需要时在对象属性添加引号
|
||||
jsxSingleQuote: false, //在JSX中使用双引号
|
||||
trailingComma: 'es5', //使用尾逗号(对象、数组等)
|
||||
bracketSpacing: true, //对象的括号间增加空格
|
||||
bracketSameLine: false, //将多行JSX元素的>放在最后一行的末尾
|
||||
arrowParens: 'avoid', //在唯一的arrow函数参数周围省略括号
|
||||
vueIndentScriptAndStyle: false, //不缩进Vue文件中的<script>和<style>标记内的代码
|
||||
endOfLine: 'lf', //仅限换行(\n)
|
||||
};
|
|
@ -0,0 +1,109 @@
|
|||
# Horizon-router
|
||||
|
||||
Horizon-router 是Horizon生态组建的一部分,为Horizon提供前端路由的能力,是构建大型应用必要组件。
|
||||
|
||||
Horizon-router涵盖react-router、history、connect-react-router的功能。
|
||||
|
||||
## 从react-router切换
|
||||
|
||||
Horizon-router 在API设计上兼容react-router V5。
|
||||
|
||||
在切换时只需在`package.json`的dependencies中加入`horizon-router`
|
||||
,并将原有的API引用从`react-router-dom`、`history`、`connected-react-router
|
||||
`切换到`horizon-router`。
|
||||
|
||||
切换样例代码如下:
|
||||
|
||||
### react-router-dom
|
||||
|
||||
**切换前**
|
||||
|
||||
```typescript
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
```
|
||||
|
||||
**切换后**
|
||||
|
||||
```typescript
|
||||
import { Switch, Route } from 'horizon-router';
|
||||
```
|
||||
|
||||
### history
|
||||
|
||||
**切换前**
|
||||
|
||||
```typescript
|
||||
import { createHashHistory, createBrowserHistory } from 'history';
|
||||
```
|
||||
|
||||
**切换后**
|
||||
|
||||
```typescript
|
||||
import { createHashHistory, createBrowserHistory } from 'horizon-router';
|
||||
```
|
||||
|
||||
### connected-react-router
|
||||
|
||||
**切换前**
|
||||
|
||||
```typescript
|
||||
import { ConnectedRouter, routerMiddleware, connectRouter } from 'connected-react-router';
|
||||
```
|
||||
|
||||
**切换后**
|
||||
|
||||
```typescript
|
||||
import { ConnectedRouter, routerMiddleware, connectRouter } from 'horizon-router';
|
||||
```
|
||||
|
||||
## Horizon-router API列表
|
||||
|
||||
history 兼容API
|
||||
---
|
||||
|
||||
- createBrowserHistory
|
||||
- createHashHistory
|
||||
|
||||
react-router-dom 兼容API
|
||||
---
|
||||
|
||||
- __RouterContext
|
||||
- matchPath
|
||||
- generatePath
|
||||
- useHistory
|
||||
- useLocation
|
||||
- useParams
|
||||
- useRouteMatch
|
||||
- Route
|
||||
- Router
|
||||
- Switch
|
||||
- Redirect
|
||||
- Prompt
|
||||
- withRouter
|
||||
- HashRouter
|
||||
- BrowserRouter
|
||||
- Link
|
||||
- NavLink
|
||||
|
||||
react-router-dom 类型兼容API
|
||||
---
|
||||
|
||||
- RouteComponentProps
|
||||
- RouteChildrenProps
|
||||
- RouteProps
|
||||
|
||||
connected-react-router 兼容API
|
||||
---
|
||||
|
||||
- connectRouter
|
||||
- routerMiddleware
|
||||
- ConnectedRouter
|
||||
|
||||
connected-react-router 新增API
|
||||
---
|
||||
|
||||
- ConnectedHRouter(在HorizonX的Redux兼容模式中使用)
|
||||
|
||||
## 问题反馈
|
||||
|
||||
Horizon-router问题与bug反馈,请联系00800104 黄轩
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||
*
|
||||
* openGauss is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
*
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
export default {
|
||||
presets: ['@babel/preset-typescript', ['@babel/preset-env', { targets: { node: 'current' } }]],
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-jsx',
|
||||
[
|
||||
'@babel/plugin-transform-react-jsx',
|
||||
{
|
||||
pragma: 'React.createElement',
|
||||
pragmaFrag: 'React.Fragment',
|
||||
},
|
||||
],
|
||||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||
['@babel/plugin-proposal-private-methods', { loose: true }],
|
||||
['@babel/plugin-proposal-private-property-in-object', { loose: true }],
|
||||
'@babel/plugin-transform-object-assign',
|
||||
'@babel/plugin-transform-object-super',
|
||||
['@babel/plugin-proposal-object-rest-spread', { loose: true, useBuiltIns: true }],
|
||||
['@babel/plugin-transform-template-literals', { loose: true }],
|
||||
'@babel/plugin-transform-arrow-functions',
|
||||
'@babel/plugin-transform-literals',
|
||||
'@babel/plugin-transform-for-of',
|
||||
'@babel/plugin-transform-block-scoped-functions',
|
||||
'@babel/plugin-transform-classes',
|
||||
'@babel/plugin-transform-shorthand-properties',
|
||||
'@babel/plugin-transform-computed-properties',
|
||||
'@babel/plugin-transform-parameters',
|
||||
['@babel/plugin-transform-spread', { loose: true, useBuiltIns: true }],
|
||||
['@babel/plugin-transform-block-scoping', { throwIfClosureRequired: false }],
|
||||
['@babel/plugin-transform-destructuring', { loose: true, useBuiltIns: true }],
|
||||
'@babel/plugin-transform-runtime',
|
||||
'@babel/plugin-proposal-nullish-coalescing-operator',
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
],
|
||||
};
|
|
@ -0,0 +1,100 @@
|
|||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import babel from '@rollup/plugin-babel';
|
||||
import nodeResolve from '@rollup/plugin-node-resolve';
|
||||
import execute from 'rollup-plugin-execute';
|
||||
import fs from 'fs';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const routerEntry = path.join(__dirname, '/src/router/index.ts');
|
||||
const connectRouterEntry = path.join(__dirname, '/src/router/index2.ts');
|
||||
|
||||
const output = __dirname;
|
||||
|
||||
const extensions = ['.js', '.ts', '.tsx'];
|
||||
|
||||
if (!fs.existsSync(path.join(output, 'connectRouter'))) {
|
||||
fs.mkdirSync(path.join(output, 'connectRouter'), { recursive: true });
|
||||
}
|
||||
|
||||
const routerBuildConfig = {
|
||||
input: { router: routerEntry },
|
||||
output: [
|
||||
{
|
||||
dir: path.resolve(output, 'router/cjs'),
|
||||
sourcemap: 'inline',
|
||||
format: 'cjs',
|
||||
},
|
||||
{
|
||||
dir: path.resolve(output, 'router/esm'),
|
||||
sourcemap: 'inline',
|
||||
format: 'esm',
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
nodeResolve({
|
||||
extensions,
|
||||
modulesOnly: true,
|
||||
}),
|
||||
babel({
|
||||
exclude: 'node_modules/**',
|
||||
configFile: path.join(__dirname, '/babel.config.js'),
|
||||
babelHelpers: 'runtime',
|
||||
extensions,
|
||||
}),
|
||||
execute('npm run build-types-router'),
|
||||
],
|
||||
};
|
||||
|
||||
const connectRouterConfig = {
|
||||
input: { connectRouter: connectRouterEntry },
|
||||
output: [
|
||||
{
|
||||
dir: path.resolve(output, 'connectRouter/cjs'),
|
||||
sourcemap: 'inline',
|
||||
format: 'cjs',
|
||||
},
|
||||
{
|
||||
dir: path.resolve(output, 'connectRouter/esm'),
|
||||
sourcemap: 'inline',
|
||||
format: 'esm',
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
nodeResolve({
|
||||
extensions,
|
||||
modulesOnly: true,
|
||||
}),
|
||||
babel({
|
||||
exclude: 'node_modules/**',
|
||||
configFile: path.join(__dirname, '/babel.config.js'),
|
||||
babelHelpers: 'runtime',
|
||||
extensions,
|
||||
}),
|
||||
execute('npm run build-types-all'),
|
||||
copyFiles([
|
||||
{
|
||||
from: path.join(__dirname, 'src/configs/package.json'),
|
||||
to: path.join(output, '/connectRouter/package.json'),
|
||||
},
|
||||
]),
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
function copyFiles(copyPairs) {
|
||||
return {
|
||||
name: 'copy-files',
|
||||
generateBundle() {
|
||||
copyPairs.forEach(({ from, to }) => {
|
||||
console.log(`copy files: ${from} → ${to}`);
|
||||
fs.copyFileSync(from, to);
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export default [routerBuildConfig, connectRouterConfig];
|
|
@ -0,0 +1,25 @@
|
|||
import { Action, Path } from '../history/types';
|
||||
type Location = Partial<Path>;
|
||||
export declare enum ActionName {
|
||||
LOCATION_CHANGE = "$horizon-router/LOCATION_CHANGE",
|
||||
CALL_HISTORY_METHOD = "$horizon-router/CALL_HISTORY_METHOD"
|
||||
}
|
||||
export type ActionMessage = {
|
||||
type: ActionName.LOCATION_CHANGE;
|
||||
payload: {
|
||||
location: Location;
|
||||
action: Action;
|
||||
isFirstRendering: boolean;
|
||||
};
|
||||
} | {
|
||||
type: ActionName.CALL_HISTORY_METHOD;
|
||||
payload: {
|
||||
method: string;
|
||||
args: any;
|
||||
};
|
||||
};
|
||||
export declare const onLocationChanged: (location: Location, action: Action, isFirstRendering?: boolean) => ActionMessage;
|
||||
export declare const push: (...args: any) => ActionMessage;
|
||||
export declare const replace: (...args: any) => ActionMessage;
|
||||
export declare const go: (...args: any) => ActionMessage;
|
||||
export {};
|
|
@ -0,0 +1,3 @@
|
|||
import { ActionMessage } from './actions';
|
||||
import { History } from '../history/types';
|
||||
export declare function routerMiddleware(history: History): (_: any) => (next: any) => (action: ActionMessage) => any;
|
|
@ -0,0 +1,11 @@
|
|||
export { getConnectedRouter } from './connectedRouter';
|
||||
export declare const connectRouter: (history: import("../router").History<unknown>) => (state?: {
|
||||
location: Partial<import("../router").Location<unknown>> & {
|
||||
query?: Record<string, any>;
|
||||
};
|
||||
action: import("../history/types").Action;
|
||||
}, { type, payload }?: {
|
||||
type?: import("./actions").ActionName;
|
||||
payload?: any;
|
||||
}) => any;
|
||||
export { routerMiddleware } from './dispatch';
|
|
@ -0,0 +1,16 @@
|
|||
import { ActionName } from './actions';
|
||||
import { Action, History } from '../history/types';
|
||||
import { Location } from '../router';
|
||||
type LocationWithQuery = Partial<Location> & {
|
||||
query?: Record<string, any>;
|
||||
};
|
||||
type InitRouterState = {
|
||||
location: LocationWithQuery;
|
||||
action: Action;
|
||||
};
|
||||
type Payload = {
|
||||
type?: ActionName;
|
||||
payload?: any;
|
||||
};
|
||||
export declare function createConnectRouter(): (history: History) => (state?: InitRouterState, { type, payload }?: Payload) => any;
|
||||
export {};
|
|
@ -0,0 +1,10 @@
|
|||
import { HistoryProps, Listener, Navigation, Prompt } from './types';
|
||||
import transitionManager from './transitionManager';
|
||||
export declare function getBaseHistory<S>(transitionManager: transitionManager<S>, setListener: (delta: number) => void, browserHistory: History): {
|
||||
go: (step: number) => void;
|
||||
goBack: () => void;
|
||||
goForward: () => void;
|
||||
listen: (listener: Listener<S>) => () => void;
|
||||
block: (prompt?: Prompt<S>) => () => void;
|
||||
getUpdateStateFunc: (historyProps: HistoryProps<S>) => (nextState: Navigation<S> | undefined) => void;
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
import { BaseOption, DefaultStateType, History } from './types';
|
||||
export type BrowserHistoryOption = {
|
||||
/**
|
||||
* forceRefresh为True时跳转时会强制刷新页面
|
||||
*/
|
||||
forceRefresh?: boolean;
|
||||
} & BaseOption;
|
||||
export declare function createBrowserHistory<S = DefaultStateType>(options?: BrowserHistoryOption): History<S>;
|
|
@ -0,0 +1,4 @@
|
|||
export declare function isBrowser(): boolean;
|
||||
export declare function getDefaultConfirmation(message: string, callBack: (result: boolean) => void): void;
|
||||
export declare function isSupportHistory(): boolean;
|
||||
export declare function isSupportsPopState(): boolean;
|
|
@ -0,0 +1,7 @@
|
|||
import { BaseOption, DefaultStateType, History } from './types';
|
||||
export type urlHashType = 'slash' | 'noslash';
|
||||
type HashHistoryOption = {
|
||||
hashType?: urlHashType;
|
||||
} & BaseOption;
|
||||
export declare function createHashHistory<S = DefaultStateType>(option?: HashHistoryOption): History<S>;
|
||||
export {};
|
|
@ -0,0 +1,11 @@
|
|||
import { Action, CallBackFunc, ConfirmationFunc, Listener, Location, Navigation, Prompt, TManager } from './types';
|
||||
declare class TransitionManager<S> implements TManager<S> {
|
||||
private prompt;
|
||||
private listeners;
|
||||
constructor();
|
||||
setPrompt(prompt: Prompt<S>): () => void;
|
||||
addListener(func: Listener<S>): () => void;
|
||||
notifyListeners(args: Navigation<S>): void;
|
||||
confirmJumpTo(location: Location<S>, action: Action, userConfirmationFunc: ConfirmationFunc, callBack: CallBackFunc): void;
|
||||
}
|
||||
export default TransitionManager;
|
|
@ -0,0 +1,56 @@
|
|||
export type BaseOption = {
|
||||
basename?: string;
|
||||
getUserConfirmation?: ConfirmationFunc;
|
||||
};
|
||||
export interface HistoryProps<T = unknown> {
|
||||
readonly action: Action;
|
||||
readonly location: Location<T>;
|
||||
length: number;
|
||||
}
|
||||
export interface History<T = unknown> extends HistoryProps<T> {
|
||||
createHref(path: Partial<Path>): string;
|
||||
push(to: To, state?: T): void;
|
||||
replace(to: To, state?: T): void;
|
||||
listen(listener: Listener<T>): () => void;
|
||||
block(prompt: Prompt<T>): () => void;
|
||||
go(index: number): void;
|
||||
goBack(): void;
|
||||
goForward(): void;
|
||||
}
|
||||
export declare enum Action {
|
||||
pop = "POP",
|
||||
push = "PUSH",
|
||||
replace = "REPLACE"
|
||||
}
|
||||
export declare enum EventType {
|
||||
PopState = "popstate",
|
||||
HashChange = "hashchange"
|
||||
}
|
||||
export type Path = {
|
||||
pathname: string;
|
||||
search: string;
|
||||
hash: string;
|
||||
};
|
||||
export type HistoryState<T> = {
|
||||
state?: T;
|
||||
key: string;
|
||||
};
|
||||
export type DefaultStateType = unknown;
|
||||
export type Location<T = unknown> = Path & HistoryState<T>;
|
||||
export type To = string | Partial<Path>;
|
||||
export interface Listener<T = unknown> {
|
||||
(navigation: Navigation<T>): void;
|
||||
}
|
||||
export interface Navigation<T = unknown> {
|
||||
action: Action;
|
||||
location: Location<T>;
|
||||
}
|
||||
export type Prompt<S> = string | boolean | null | ((location: Location<S>, action: Action) => void);
|
||||
export type CallBackFunc = (isJump: boolean) => void;
|
||||
export type ConfirmationFunc = (message: string, callBack: CallBackFunc) => void;
|
||||
export interface TManager<S> {
|
||||
setPrompt(next: Prompt<S>): () => void;
|
||||
addListener(func: (navigation: Navigation<S>) => void): () => void;
|
||||
notifyListeners(args: Navigation<S>): void;
|
||||
confirmJumpTo(location: Location<S>, action: Action, userConfirmationFunc: ConfirmationFunc, callBack: CallBackFunc): void;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import { Action, Location, Path, To } from './types';
|
||||
export declare function createPath(path: Partial<Path>): string;
|
||||
export declare function parsePath(url: string): Partial<Path>;
|
||||
export declare function createLocation<S>(current: string | Location, to: To, state?: S, key?: string): Readonly<Location<S>>;
|
||||
export declare function isLocationEqual(p1: Partial<Path>, p2: Partial<Path>): boolean;
|
||||
export declare function addHeadSlash(path: string): string;
|
||||
export declare function stripHeadSlash(path: string): string;
|
||||
export declare function normalizeSlash(path: string): string;
|
||||
export declare function hasBasename(path: string, prefix: string): Boolean;
|
||||
export declare function stripBasename(path: string, prefix: string): string;
|
||||
export declare function createMemoryRecord<T, S>(initVal: S, fn: (arg: S) => T): {
|
||||
getDelta: (to: S, form: S) => number;
|
||||
addRecord: (current: S, newRecord: S, action: Action) => void;
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
declare function warning(condition: any, message: string): void;
|
||||
export default warning;
|
|
@ -0,0 +1,12 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { ConfirmationFunc } from '../history/types';
|
||||
export type BaseRouterProps = {
|
||||
basename: string;
|
||||
getUserConfirmation: ConfirmationFunc;
|
||||
children?: ReactNode;
|
||||
};
|
||||
export type BrowserRouterProps = BaseRouterProps & {
|
||||
forceRefresh: boolean;
|
||||
};
|
||||
declare function BrowserRouter<P extends Partial<BrowserRouterProps>>(props: P): JSX.Element;
|
||||
export default BrowserRouter;
|
|
@ -0,0 +1,7 @@
|
|||
import { BaseRouterProps } from './BrowserRouter';
|
||||
import { urlHashType } from '../history/hashHistory';
|
||||
export type HashRouterProps = BaseRouterProps & {
|
||||
hashType: urlHashType;
|
||||
};
|
||||
declare function HashRouter<P extends Partial<HashRouterProps>>(props: P): JSX.Element;
|
||||
export default HashRouter;
|
|
@ -0,0 +1,18 @@
|
|||
import * as React from 'react';
|
||||
import { Location } from './index';
|
||||
export type LinkProps = {
|
||||
component?: React.ComponentType<any>;
|
||||
to: Partial<Location> | string | ((location: Location) => string | Partial<Location>);
|
||||
replace?: boolean;
|
||||
tag?: string;
|
||||
/**
|
||||
* @deprecated
|
||||
* React16以后不再需要该属性
|
||||
**/
|
||||
innerRef?: React.Ref<HTMLAnchorElement>;
|
||||
} & React.AnchorHTMLAttributes<HTMLAnchorElement>;
|
||||
declare function Link<P extends LinkProps>(props: P): React.DOMElement<{
|
||||
href: string;
|
||||
onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
|
||||
} & Omit<P, "replace" | "to" | "component" | "onClick" | "target">, Element>;
|
||||
export default Link;
|
|
@ -0,0 +1,10 @@
|
|||
import type { LinkProps } from './Link';
|
||||
import { Location } from './index';
|
||||
import { Matched } from './matcher/parser';
|
||||
type NavLinkProps = {
|
||||
to: Partial<Location> | string | ((location: Location) => string | Partial<Location>);
|
||||
isActive?: (match: Matched | null, location: Location) => boolean;
|
||||
[key: string]: any;
|
||||
} & LinkProps;
|
||||
declare function NavLink<P extends NavLinkProps>(props: P): JSX.Element;
|
||||
export default NavLink;
|
|
@ -0,0 +1,8 @@
|
|||
import { Location } from './index';
|
||||
import { Action } from '../history/types';
|
||||
type PromptProps = {
|
||||
message?: string | ((location: Partial<Location>, action: Action) => void);
|
||||
when?: boolean | ((location: Partial<Location>) => boolean);
|
||||
};
|
||||
declare function Prompt<P extends PromptProps>(props: P): JSX.Element;
|
||||
export default Prompt;
|
|
@ -0,0 +1,13 @@
|
|||
import { Matched } from './matcher/parser';
|
||||
import { Location } from './index';
|
||||
export type RedirectProps = {
|
||||
to: string | Partial<Location>;
|
||||
push?: boolean;
|
||||
path?: string;
|
||||
from?: string;
|
||||
exact?: boolean;
|
||||
strict?: boolean;
|
||||
readonly computed?: Matched | null;
|
||||
};
|
||||
declare function Redirect<P extends RedirectProps>(props: P): JSX.Element;
|
||||
export default Redirect;
|
|
@ -0,0 +1,23 @@
|
|||
import * as React from 'react';
|
||||
import { History, Location } from './index';
|
||||
import { Matched } from './matcher/parser';
|
||||
import { GetURLParams } from './matcher/types';
|
||||
export type RouteComponentProps<P extends Record<string, any> = {}, S = unknown> = RouteChildrenProps<P, S>;
|
||||
export type RouteChildrenProps<P extends Record<string, any> = {}, S = unknown> = {
|
||||
history: History<S>;
|
||||
location: Location<S>;
|
||||
match: Matched<P> | null;
|
||||
};
|
||||
export type RouteProps<P extends Record<string, any> = {}, Path extends string = string> = {
|
||||
location?: Location;
|
||||
component?: React.ComponentType<RouteComponentProps<P>> | React.ComponentType<any> | undefined;
|
||||
children?: ((props: RouteChildrenProps<P>) => React.ReactNode) | React.ReactNode;
|
||||
render?: (props: RouteComponentProps<P>) => React.ReactNode;
|
||||
path?: Path | Path[];
|
||||
exact?: boolean;
|
||||
sensitive?: boolean;
|
||||
strict?: boolean;
|
||||
computed?: Matched<P>;
|
||||
};
|
||||
declare function Route<Path extends string, P extends Record<string, any> = GetURLParams<Path>>(props: RouteProps<P, Path>): JSX.Element;
|
||||
export default Route;
|
|
@ -0,0 +1,8 @@
|
|||
import * as React from 'react';
|
||||
import { History } from '../history/types';
|
||||
export type RouterProps = {
|
||||
history: History;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
declare function Router<P extends RouterProps>(props: P): JSX.Element;
|
||||
export default Router;
|
|
@ -0,0 +1,8 @@
|
|||
import * as React from 'react';
|
||||
import { Location } from './index';
|
||||
export type SwitchProps = {
|
||||
location?: Location;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
declare function Switch<P extends SwitchProps>(props: P): React.ReactElement | null;
|
||||
export default Switch;
|
1
packages/horizon-router/connectRouter/@types/router/__tests__/router.test.d.ts
vendored
Normal file
1
packages/horizon-router/connectRouter/@types/router/__tests__/router.test.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
import '@testing-library/jest-dom';
|
|
@ -0,0 +1,8 @@
|
|||
import { History, Location } from '../index';
|
||||
export declare let historyHook: History;
|
||||
export declare let locationHook: Location;
|
||||
export declare const LocationDisplay: () => JSX.Element;
|
||||
export declare const Test_Demo: () => JSX.Element;
|
||||
export declare const Test_Demo2: () => JSX.Element;
|
||||
export declare const Test_Demo3: () => JSX.Element;
|
||||
export declare const Test_Demo4: () => JSX.Element;
|
|
@ -0,0 +1,10 @@
|
|||
/// <reference types="react" />
|
||||
import { History, Location } from './index';
|
||||
import { Matched } from './matcher/parser';
|
||||
export type RouterContextValue = {
|
||||
history: History;
|
||||
location: Location;
|
||||
match: Matched | null;
|
||||
};
|
||||
declare const RouterContext: import("react").Context<RouterContextValue>;
|
||||
export default RouterContext;
|
|
@ -0,0 +1,8 @@
|
|||
import { Matched, Params } from './matcher/parser';
|
||||
import { History } from '../history/types';
|
||||
import { Location } from './index';
|
||||
declare function useHistory<S>(): History<S>;
|
||||
declare function useLocation<S>(): Location<S>;
|
||||
declare function useParams<P>(): Params<P> | {};
|
||||
declare function useRouteMatch<P>(path?: string): Matched<P> | null;
|
||||
export { useHistory, useLocation, useParams, useRouteMatch };
|
|
@ -0,0 +1,20 @@
|
|||
import { Location as HLocation } from '../history/types';
|
||||
type Location<S = unknown> = Omit<HLocation<S>, 'key'>;
|
||||
export { Location };
|
||||
export type { History } from '../history/types';
|
||||
export { createBrowserHistory } from '../history/browerHistory';
|
||||
export { createHashHistory } from '../history/hashHistory';
|
||||
export { default as __RouterContext } from './context';
|
||||
export { matchPath, generatePath } from './matcher/parser';
|
||||
export { useHistory, useLocation, useParams, useRouteMatch } from './hooks';
|
||||
export { default as Route } from './Route';
|
||||
export { default as Router } from './Router';
|
||||
export { default as Switch } from './Switch';
|
||||
export { default as Redirect } from './Redirect';
|
||||
export { default as Prompt } from './Prompt';
|
||||
export { default as withRouter } from './withRouter';
|
||||
export { default as HashRouter } from './HashRouter';
|
||||
export { default as BrowserRouter } from './BrowserRouter';
|
||||
export { default as Link } from './Link';
|
||||
export { default as NavLink } from './NavLink';
|
||||
export type { RouteComponentProps, RouteChildrenProps, RouteProps } from './Route';
|
|
@ -0,0 +1,23 @@
|
|||
import { Location as HLocation } from '../history/types';
|
||||
type Location<S = unknown> = Omit<HLocation<S>, 'key'>;
|
||||
export { Location };
|
||||
export type { History } from '../history/types';
|
||||
export { createBrowserHistory } from '../history/browerHistory';
|
||||
export { createHashHistory } from '../history/hashHistory';
|
||||
export { default as __RouterContext } from './context';
|
||||
export { matchPath, generatePath } from './matcher/parser';
|
||||
export { useHistory, useLocation, useParams, useRouteMatch } from './hooks';
|
||||
export { default as Route } from './Route';
|
||||
export { default as Router } from './Router';
|
||||
export { default as Switch } from './Switch';
|
||||
export { default as Redirect } from './Redirect';
|
||||
export { default as Prompt } from './Prompt';
|
||||
export { default as withRouter } from './withRouter';
|
||||
export { default as HashRouter } from './HashRouter';
|
||||
export { default as BrowserRouter } from './BrowserRouter';
|
||||
export { default as Link } from './Link';
|
||||
export { default as NavLink } from './NavLink';
|
||||
export type { RouteComponentProps, RouteChildrenProps, RouteProps } from './Route';
|
||||
export { connectRouter, routerMiddleware } from '../connect-router';
|
||||
export declare const ConnectedRouter: any;
|
||||
export declare const ConnectedHRouter: any;
|
|
@ -0,0 +1,7 @@
|
|||
export type LifeCycleProps = {
|
||||
onMount?: () => void;
|
||||
onUpdate?: (prevProps?: LifeCycleProps) => void;
|
||||
onUnmount?: () => void;
|
||||
data?: any;
|
||||
};
|
||||
export declare function LifeCycle(props: LifeCycleProps): any;
|
1
packages/horizon-router/connectRouter/@types/router/matcher/__tests__/lexer.test.d.ts
vendored
Normal file
1
packages/horizon-router/connectRouter/@types/router/matcher/__tests__/lexer.test.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export {};
|
1
packages/horizon-router/connectRouter/@types/router/matcher/__tests__/parser.test.d.ts
vendored
Normal file
1
packages/horizon-router/connectRouter/@types/router/matcher/__tests__/parser.test.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export {};
|
1
packages/horizon-router/connectRouter/@types/router/matcher/__tests__/utils.test.d.ts
vendored
Normal file
1
packages/horizon-router/connectRouter/@types/router/matcher/__tests__/utils.test.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export {};
|
|
@ -0,0 +1,2 @@
|
|||
import { Token } from './types';
|
||||
export declare function lexer(path: string): Token[];
|
|
@ -0,0 +1,18 @@
|
|||
import { GetURLParams, Parser, ParserOption } from './types';
|
||||
export type Params<P> = {
|
||||
[K in keyof P]?: P[K];
|
||||
};
|
||||
export type Matched<P = any> = {
|
||||
score: number[];
|
||||
params: Params<P>;
|
||||
path: string;
|
||||
url: string;
|
||||
isExact: boolean;
|
||||
};
|
||||
export declare function createPathParser<Str extends string>(pathname: Str, option?: ParserOption): Parser<GetURLParams<Str>>;
|
||||
export declare function createPathParser<P = unknown>(pathname: string, option?: ParserOption): Parser<P>;
|
||||
/**
|
||||
* @description 依次使用pathname与pattern进行匹配,根据匹配分数取得分数最高结果
|
||||
*/
|
||||
export declare function matchPath<P = any>(pathname: string, pattern: string | string[], option?: ParserOption): Matched<P> | null;
|
||||
export declare function generatePath<P = any>(path: string, params: Params<P>): string;
|
|
@ -0,0 +1,36 @@
|
|||
import { Matched, Params } from './parser';
|
||||
export type Token = {
|
||||
type: TokenType;
|
||||
value: string;
|
||||
};
|
||||
export declare enum TokenType {
|
||||
Delimiter = "delimiter",
|
||||
Static = "static",
|
||||
Param = "param",
|
||||
WildCard = "wildcard",
|
||||
LBracket = "(",
|
||||
RBracket = ")",
|
||||
Pattern = "pattern"
|
||||
}
|
||||
export interface Parser<P> {
|
||||
regexp: RegExp;
|
||||
keys: string[];
|
||||
parse(url: string): Matched<P> | null;
|
||||
compile(params: Params<P>): string;
|
||||
}
|
||||
export type ParserOption = {
|
||||
caseSensitive?: boolean;
|
||||
strictMode?: boolean;
|
||||
exact?: boolean;
|
||||
};
|
||||
type ClearLeading<U extends string> = U extends `/${infer R}` ? ClearLeading<R> : U;
|
||||
type ClearTailing<U extends string> = U extends `${infer L}/` ? ClearTailing<L> : U;
|
||||
type ParseParam<Param extends string> = Param extends `:${infer R}` ? {
|
||||
[K in R]: string;
|
||||
} : {};
|
||||
type MergeParams<OneParam extends Record<string, any>, OtherParam extends Record<string, any>> = {
|
||||
readonly [Key in keyof OneParam | keyof OtherParam]?: string;
|
||||
};
|
||||
type ParseURLString<Str extends string> = Str extends `${infer Param}/${infer Rest}` ? MergeParams<ParseParam<Param>, ParseURLString<ClearLeading<Rest>>> : ParseParam<Str>;
|
||||
export type GetURLParams<U extends string> = ParseURLString<ClearLeading<ClearTailing<U>>>;
|
||||
export {};
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* @description 将url中的//转换为/
|
||||
*/
|
||||
export declare function cleanPath(path: string): string;
|
||||
export declare function scoreCompare(score1: number[], score2: number[]): number;
|
||||
export declare function escapeStr(str: string): string;
|
|
@ -0,0 +1,3 @@
|
|||
import * as React from 'react';
|
||||
declare function withRouter<C extends React.ComponentType>(Component: C): (props: any) => JSX.Element;
|
||||
export default withRouter;
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"module": "./esm/connectRouter.js",
|
||||
"main": "./cjs/connectRouter.js",
|
||||
"types": "./@types/router/index2.d.ts",
|
||||
"peerDependencies": {
|
||||
"react-redux": "^6.0.0 || ^7.1.0",
|
||||
"redux": "^3.6.0 || ^4.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
preset: "ts-jest",
|
||||
testMatch: ["**/__tests__/*.test.[jt]s?(x)"],
|
||||
globals: {
|
||||
__DEV__: true,
|
||||
},
|
||||
testEnvironment: 'jsdom'
|
||||
};
|
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"name": "@cloudsop/horizon-router",
|
||||
"version": "0.0.12",
|
||||
"description": "router for horizon framework, a part of horizon-ecosystem",
|
||||
"main": "./router/cjs/router.js",
|
||||
"module": "./router/esm/router.js",
|
||||
"types": "./router/@types/router/index.d.ts",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"/connectRouter",
|
||||
"/router",
|
||||
"README.md"
|
||||
],
|
||||
"bugs": {
|
||||
"url": "https://open.codehub.huawei.com/innersource/fenghuang/horizon/horizon-ecosystem/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"build": "rollup -c build.js",
|
||||
"build-types-router": "tsc -p src/router/index.ts --emitDeclarationOnly --declaration --declarationDir ./router/@types --skipLibCheck",
|
||||
"build-types-all": "tsc -p src/router/index2.ts --emitDeclarationOnly --declaration --declarationDir ./connectRouter/@types --skipLibCheck"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://szv-open.codehub.huawei.com/innersource/fenghuang/horizon/horizon-ecosystem.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.21.3",
|
||||
"@babel/plugin-proposal-class-properties": "7.16.7",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7",
|
||||
"@babel/plugin-proposal-object-rest-spread": "7.16.7",
|
||||
"@babel/plugin-proposal-optional-chaining": "7.16.7",
|
||||
"@babel/plugin-syntax-jsx": "7.16.7",
|
||||
"@babel/plugin-transform-arrow-functions": "7.16.7",
|
||||
"@babel/plugin-transform-block-scoped-functions": "7.16.7",
|
||||
"@babel/plugin-transform-block-scoping": "7.16.7",
|
||||
"@babel/plugin-transform-classes": "7.16.7",
|
||||
"@babel/plugin-transform-computed-properties": "7.16.7",
|
||||
"@babel/plugin-transform-destructuring": "7.16.7",
|
||||
"@babel/plugin-transform-for-of": "7.16.7",
|
||||
"@babel/plugin-transform-literals": "7.16.7",
|
||||
"@babel/plugin-transform-object-assign": "7.16.7",
|
||||
"@babel/plugin-transform-object-super": "7.16.7",
|
||||
"@babel/plugin-transform-parameters": "7.16.7",
|
||||
"@babel/plugin-transform-react-jsx": "7.16.7",
|
||||
"@babel/plugin-transform-react-jsx-source": "^7.16.7",
|
||||
"@babel/plugin-transform-runtime": "7.16.7",
|
||||
"@babel/plugin-transform-shorthand-properties": "7.16.7",
|
||||
"@babel/plugin-transform-spread": "7.16.7",
|
||||
"@babel/plugin-transform-template-literals": "7.16.7",
|
||||
"@babel/preset-env": "7.16.7",
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@cloudsop/horizon": "0.0.52",
|
||||
"@rollup/plugin-babel": "^6.0.3",
|
||||
"@rollup/plugin-node-resolve": "^15.1.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/jest": "^29.2.3",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react-redux": "7.0.0",
|
||||
"jest": "29.3.1",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"jsdom": "^21.1.1",
|
||||
"prettier": "2.8.8",
|
||||
"react-redux": "^3.8.1 || ^4.0.0",
|
||||
"rollup": "2.79.1",
|
||||
"rollup-plugin-execute": "^1.1.1",
|
||||
"ts-jest": "29.0.3",
|
||||
"typescript": "4.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudsop/horizon": "^0.0.52",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"engineStrict": true
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { Action, Path } from '../history/types';
|
||||
type Location = Partial<Path>;
|
||||
export declare enum ActionName {
|
||||
LOCATION_CHANGE = "$horizon-router/LOCATION_CHANGE",
|
||||
CALL_HISTORY_METHOD = "$horizon-router/CALL_HISTORY_METHOD"
|
||||
}
|
||||
export type ActionMessage = {
|
||||
type: ActionName.LOCATION_CHANGE;
|
||||
payload: {
|
||||
location: Location;
|
||||
action: Action;
|
||||
isFirstRendering: boolean;
|
||||
};
|
||||
} | {
|
||||
type: ActionName.CALL_HISTORY_METHOD;
|
||||
payload: {
|
||||
method: string;
|
||||
args: any;
|
||||
};
|
||||
};
|
||||
export declare const onLocationChanged: (location: Location, action: Action, isFirstRendering?: boolean) => ActionMessage;
|
||||
export declare const push: (...args: any) => ActionMessage;
|
||||
export declare const replace: (...args: any) => ActionMessage;
|
||||
export declare const go: (...args: any) => ActionMessage;
|
||||
export {};
|
|
@ -0,0 +1,3 @@
|
|||
import { ActionMessage } from './actions';
|
||||
import { History } from '../history/types';
|
||||
export declare function routerMiddleware(history: History): (_: any) => (next: any) => (action: ActionMessage) => any;
|
|
@ -0,0 +1,11 @@
|
|||
export { getConnectedRouter } from './connectedRouter';
|
||||
export declare const connectRouter: (history: import("../router").History<unknown>) => (state?: {
|
||||
location: Partial<import("../router").Location<unknown>> & {
|
||||
query?: Record<string, any>;
|
||||
};
|
||||
action: import("../history/types").Action;
|
||||
}, { type, payload }?: {
|
||||
type?: import("./actions").ActionName;
|
||||
payload?: any;
|
||||
}) => any;
|
||||
export { routerMiddleware } from './dispatch';
|
|
@ -0,0 +1,16 @@
|
|||
import { ActionName } from './actions';
|
||||
import { Action, History } from '../history/types';
|
||||
import { Location } from '../router';
|
||||
type LocationWithQuery = Partial<Location> & {
|
||||
query?: Record<string, any>;
|
||||
};
|
||||
type InitRouterState = {
|
||||
location: LocationWithQuery;
|
||||
action: Action;
|
||||
};
|
||||
type Payload = {
|
||||
type?: ActionName;
|
||||
payload?: any;
|
||||
};
|
||||
export declare function createConnectRouter(): (history: History) => (state?: InitRouterState, { type, payload }?: Payload) => any;
|
||||
export {};
|
|
@ -0,0 +1,10 @@
|
|||
import { HistoryProps, Listener, Navigation, Prompt } from './types';
|
||||
import transitionManager from './transitionManager';
|
||||
export declare function getBaseHistory<S>(transitionManager: transitionManager<S>, setListener: (delta: number) => void, browserHistory: History): {
|
||||
go: (step: number) => void;
|
||||
goBack: () => void;
|
||||
goForward: () => void;
|
||||
listen: (listener: Listener<S>) => () => void;
|
||||
block: (prompt?: Prompt<S>) => () => void;
|
||||
getUpdateStateFunc: (historyProps: HistoryProps<S>) => (nextState: Navigation<S> | undefined) => void;
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
import { BaseOption, DefaultStateType, History } from './types';
|
||||
export type BrowserHistoryOption = {
|
||||
/**
|
||||
* forceRefresh为True时跳转时会强制刷新页面
|
||||
*/
|
||||
forceRefresh?: boolean;
|
||||
} & BaseOption;
|
||||
export declare function createBrowserHistory<S = DefaultStateType>(options?: BrowserHistoryOption): History<S>;
|
|
@ -0,0 +1,4 @@
|
|||
export declare function isBrowser(): boolean;
|
||||
export declare function getDefaultConfirmation(message: string, callBack: (result: boolean) => void): void;
|
||||
export declare function isSupportHistory(): boolean;
|
||||
export declare function isSupportsPopState(): boolean;
|
|
@ -0,0 +1,7 @@
|
|||
import { BaseOption, DefaultStateType, History } from './types';
|
||||
export type urlHashType = 'slash' | 'noslash';
|
||||
type HashHistoryOption = {
|
||||
hashType?: urlHashType;
|
||||
} & BaseOption;
|
||||
export declare function createHashHistory<S = DefaultStateType>(option?: HashHistoryOption): History<S>;
|
||||
export {};
|
|
@ -0,0 +1,11 @@
|
|||
import { Action, CallBackFunc, ConfirmationFunc, Listener, Location, Navigation, Prompt, TManager } from './types';
|
||||
declare class TransitionManager<S> implements TManager<S> {
|
||||
private prompt;
|
||||
private listeners;
|
||||
constructor();
|
||||
setPrompt(prompt: Prompt<S>): () => void;
|
||||
addListener(func: Listener<S>): () => void;
|
||||
notifyListeners(args: Navigation<S>): void;
|
||||
confirmJumpTo(location: Location<S>, action: Action, userConfirmationFunc: ConfirmationFunc, callBack: CallBackFunc): void;
|
||||
}
|
||||
export default TransitionManager;
|
|
@ -0,0 +1,56 @@
|
|||
export type BaseOption = {
|
||||
basename?: string;
|
||||
getUserConfirmation?: ConfirmationFunc;
|
||||
};
|
||||
export interface HistoryProps<T = unknown> {
|
||||
readonly action: Action;
|
||||
readonly location: Location<T>;
|
||||
length: number;
|
||||
}
|
||||
export interface History<T = unknown> extends HistoryProps<T> {
|
||||
createHref(path: Partial<Path>): string;
|
||||
push(to: To, state?: T): void;
|
||||
replace(to: To, state?: T): void;
|
||||
listen(listener: Listener<T>): () => void;
|
||||
block(prompt: Prompt<T>): () => void;
|
||||
go(index: number): void;
|
||||
goBack(): void;
|
||||
goForward(): void;
|
||||
}
|
||||
export declare enum Action {
|
||||
pop = "POP",
|
||||
push = "PUSH",
|
||||
replace = "REPLACE"
|
||||
}
|
||||
export declare enum EventType {
|
||||
PopState = "popstate",
|
||||
HashChange = "hashchange"
|
||||
}
|
||||
export type Path = {
|
||||
pathname: string;
|
||||
search: string;
|
||||
hash: string;
|
||||
};
|
||||
export type HistoryState<T> = {
|
||||
state?: T;
|
||||
key: string;
|
||||
};
|
||||
export type DefaultStateType = unknown;
|
||||
export type Location<T = unknown> = Path & HistoryState<T>;
|
||||
export type To = string | Partial<Path>;
|
||||
export interface Listener<T = unknown> {
|
||||
(navigation: Navigation<T>): void;
|
||||
}
|
||||
export interface Navigation<T = unknown> {
|
||||
action: Action;
|
||||
location: Location<T>;
|
||||
}
|
||||
export type Prompt<S> = string | boolean | null | ((location: Location<S>, action: Action) => void);
|
||||
export type CallBackFunc = (isJump: boolean) => void;
|
||||
export type ConfirmationFunc = (message: string, callBack: CallBackFunc) => void;
|
||||
export interface TManager<S> {
|
||||
setPrompt(next: Prompt<S>): () => void;
|
||||
addListener(func: (navigation: Navigation<S>) => void): () => void;
|
||||
notifyListeners(args: Navigation<S>): void;
|
||||
confirmJumpTo(location: Location<S>, action: Action, userConfirmationFunc: ConfirmationFunc, callBack: CallBackFunc): void;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import { Action, Location, Path, To } from './types';
|
||||
export declare function createPath(path: Partial<Path>): string;
|
||||
export declare function parsePath(url: string): Partial<Path>;
|
||||
export declare function createLocation<S>(current: string | Location, to: To, state?: S, key?: string): Readonly<Location<S>>;
|
||||
export declare function isLocationEqual(p1: Partial<Path>, p2: Partial<Path>): boolean;
|
||||
export declare function addHeadSlash(path: string): string;
|
||||
export declare function stripHeadSlash(path: string): string;
|
||||
export declare function normalizeSlash(path: string): string;
|
||||
export declare function hasBasename(path: string, prefix: string): Boolean;
|
||||
export declare function stripBasename(path: string, prefix: string): string;
|
||||
export declare function createMemoryRecord<T, S>(initVal: S, fn: (arg: S) => T): {
|
||||
getDelta: (to: S, form: S) => number;
|
||||
addRecord: (current: S, newRecord: S, action: Action) => void;
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
declare function warning(condition: any, message: string): void;
|
||||
export default warning;
|
|
@ -0,0 +1,12 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { ConfirmationFunc } from '../history/types';
|
||||
export type BaseRouterProps = {
|
||||
basename: string;
|
||||
getUserConfirmation: ConfirmationFunc;
|
||||
children?: ReactNode;
|
||||
};
|
||||
export type BrowserRouterProps = BaseRouterProps & {
|
||||
forceRefresh: boolean;
|
||||
};
|
||||
declare function BrowserRouter<P extends Partial<BrowserRouterProps>>(props: P): JSX.Element;
|
||||
export default BrowserRouter;
|
|
@ -0,0 +1,7 @@
|
|||
import { BaseRouterProps } from './BrowserRouter';
|
||||
import { urlHashType } from '../history/hashHistory';
|
||||
export type HashRouterProps = BaseRouterProps & {
|
||||
hashType: urlHashType;
|
||||
};
|
||||
declare function HashRouter<P extends Partial<HashRouterProps>>(props: P): JSX.Element;
|
||||
export default HashRouter;
|
|
@ -0,0 +1,18 @@
|
|||
import * as React from 'react';
|
||||
import { Location } from './index';
|
||||
export type LinkProps = {
|
||||
component?: React.ComponentType<any>;
|
||||
to: Partial<Location> | string | ((location: Location) => string | Partial<Location>);
|
||||
replace?: boolean;
|
||||
tag?: string;
|
||||
/**
|
||||
* @deprecated
|
||||
* React16以后不再需要该属性
|
||||
**/
|
||||
innerRef?: React.Ref<HTMLAnchorElement>;
|
||||
} & React.AnchorHTMLAttributes<HTMLAnchorElement>;
|
||||
declare function Link<P extends LinkProps>(props: P): React.DOMElement<{
|
||||
href: string;
|
||||
onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
|
||||
} & Omit<P, "replace" | "to" | "component" | "onClick" | "target">, Element>;
|
||||
export default Link;
|
|
@ -0,0 +1,10 @@
|
|||
import type { LinkProps } from './Link';
|
||||
import { Location } from './index';
|
||||
import { Matched } from './matcher/parser';
|
||||
type NavLinkProps = {
|
||||
to: Partial<Location> | string | ((location: Location) => string | Partial<Location>);
|
||||
isActive?: (match: Matched | null, location: Location) => boolean;
|
||||
[key: string]: any;
|
||||
} & LinkProps;
|
||||
declare function NavLink<P extends NavLinkProps>(props: P): JSX.Element;
|
||||
export default NavLink;
|
|
@ -0,0 +1,8 @@
|
|||
import { Location } from './index';
|
||||
import { Action } from '../history/types';
|
||||
type PromptProps = {
|
||||
message?: string | ((location: Partial<Location>, action: Action) => void);
|
||||
when?: boolean | ((location: Partial<Location>) => boolean);
|
||||
};
|
||||
declare function Prompt<P extends PromptProps>(props: P): JSX.Element;
|
||||
export default Prompt;
|
|
@ -0,0 +1,13 @@
|
|||
import { Matched } from './matcher/parser';
|
||||
import { Location } from './index';
|
||||
export type RedirectProps = {
|
||||
to: string | Partial<Location>;
|
||||
push?: boolean;
|
||||
path?: string;
|
||||
from?: string;
|
||||
exact?: boolean;
|
||||
strict?: boolean;
|
||||
readonly computed?: Matched | null;
|
||||
};
|
||||
declare function Redirect<P extends RedirectProps>(props: P): JSX.Element;
|
||||
export default Redirect;
|
|
@ -0,0 +1,23 @@
|
|||
import * as React from 'react';
|
||||
import { History, Location } from './index';
|
||||
import { Matched } from './matcher/parser';
|
||||
import { GetURLParams } from './matcher/types';
|
||||
export type RouteComponentProps<P extends Record<string, any> = {}, S = unknown> = RouteChildrenProps<P, S>;
|
||||
export type RouteChildrenProps<P extends Record<string, any> = {}, S = unknown> = {
|
||||
history: History<S>;
|
||||
location: Location<S>;
|
||||
match: Matched<P> | null;
|
||||
};
|
||||
export type RouteProps<P extends Record<string, any> = {}, Path extends string = string> = {
|
||||
location?: Location;
|
||||
component?: React.ComponentType<RouteComponentProps<P>> | React.ComponentType<any> | undefined;
|
||||
children?: ((props: RouteChildrenProps<P>) => React.ReactNode) | React.ReactNode;
|
||||
render?: (props: RouteComponentProps<P>) => React.ReactNode;
|
||||
path?: Path | Path[];
|
||||
exact?: boolean;
|
||||
sensitive?: boolean;
|
||||
strict?: boolean;
|
||||
computed?: Matched<P>;
|
||||
};
|
||||
declare function Route<Path extends string, P extends Record<string, any> = GetURLParams<Path>>(props: RouteProps<P, Path>): JSX.Element;
|
||||
export default Route;
|
|
@ -0,0 +1,8 @@
|
|||
import * as React from 'react';
|
||||
import { History } from '../history/types';
|
||||
export type RouterProps = {
|
||||
history: History;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
declare function Router<P extends RouterProps>(props: P): JSX.Element;
|
||||
export default Router;
|
|
@ -0,0 +1,8 @@
|
|||
import * as React from 'react';
|
||||
import { Location } from './index';
|
||||
export type SwitchProps = {
|
||||
location?: Location;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
declare function Switch<P extends SwitchProps>(props: P): React.ReactElement | null;
|
||||
export default Switch;
|
|
@ -0,0 +1 @@
|
|||
import '@testing-library/jest-dom';
|
|
@ -0,0 +1,8 @@
|
|||
import { History, Location } from '../index';
|
||||
export declare let historyHook: History;
|
||||
export declare let locationHook: Location;
|
||||
export declare const LocationDisplay: () => JSX.Element;
|
||||
export declare const Test_Demo: () => JSX.Element;
|
||||
export declare const Test_Demo2: () => JSX.Element;
|
||||
export declare const Test_Demo3: () => JSX.Element;
|
||||
export declare const Test_Demo4: () => JSX.Element;
|
|
@ -0,0 +1,10 @@
|
|||
/// <reference types="react" />
|
||||
import { History, Location } from './index';
|
||||
import { Matched } from './matcher/parser';
|
||||
export type RouterContextValue = {
|
||||
history: History;
|
||||
location: Location;
|
||||
match: Matched | null;
|
||||
};
|
||||
declare const RouterContext: import("react").Context<RouterContextValue>;
|
||||
export default RouterContext;
|
|
@ -0,0 +1,8 @@
|
|||
import { Matched, Params } from './matcher/parser';
|
||||
import { History } from '../history/types';
|
||||
import { Location } from './index';
|
||||
declare function useHistory<S>(): History<S>;
|
||||
declare function useLocation<S>(): Location<S>;
|
||||
declare function useParams<P>(): Params<P> | {};
|
||||
declare function useRouteMatch<P>(path?: string): Matched<P> | null;
|
||||
export { useHistory, useLocation, useParams, useRouteMatch };
|
|
@ -0,0 +1,20 @@
|
|||
import { Location as HLocation } from '../history/types';
|
||||
type Location<S = unknown> = Omit<HLocation<S>, 'key'>;
|
||||
export { Location };
|
||||
export type { History } from '../history/types';
|
||||
export { createBrowserHistory } from '../history/browerHistory';
|
||||
export { createHashHistory } from '../history/hashHistory';
|
||||
export { default as __RouterContext } from './context';
|
||||
export { matchPath, generatePath } from './matcher/parser';
|
||||
export { useHistory, useLocation, useParams, useRouteMatch } from './hooks';
|
||||
export { default as Route } from './Route';
|
||||
export { default as Router } from './Router';
|
||||
export { default as Switch } from './Switch';
|
||||
export { default as Redirect } from './Redirect';
|
||||
export { default as Prompt } from './Prompt';
|
||||
export { default as withRouter } from './withRouter';
|
||||
export { default as HashRouter } from './HashRouter';
|
||||
export { default as BrowserRouter } from './BrowserRouter';
|
||||
export { default as Link } from './Link';
|
||||
export { default as NavLink } from './NavLink';
|
||||
export type { RouteComponentProps, RouteChildrenProps, RouteProps } from './Route';
|
|
@ -0,0 +1,23 @@
|
|||
import { Location as HLocation } from '../history/types';
|
||||
type Location<S = unknown> = Omit<HLocation<S>, 'key'>;
|
||||
export { Location };
|
||||
export type { History } from '../history/types';
|
||||
export { createBrowserHistory } from '../history/browerHistory';
|
||||
export { createHashHistory } from '../history/hashHistory';
|
||||
export { default as __RouterContext } from './context';
|
||||
export { matchPath, generatePath } from './matcher/parser';
|
||||
export { useHistory, useLocation, useParams, useRouteMatch } from './hooks';
|
||||
export { default as Route } from './Route';
|
||||
export { default as Router } from './Router';
|
||||
export { default as Switch } from './Switch';
|
||||
export { default as Redirect } from './Redirect';
|
||||
export { default as Prompt } from './Prompt';
|
||||
export { default as withRouter } from './withRouter';
|
||||
export { default as HashRouter } from './HashRouter';
|
||||
export { default as BrowserRouter } from './BrowserRouter';
|
||||
export { default as Link } from './Link';
|
||||
export { default as NavLink } from './NavLink';
|
||||
export type { RouteComponentProps, RouteChildrenProps, RouteProps } from './Route';
|
||||
export { connectRouter, routerMiddleware } from '../connect-router';
|
||||
export declare const ConnectedRouter: any;
|
||||
export declare const ConnectedHRouter: any;
|
|
@ -0,0 +1,7 @@
|
|||
export type LifeCycleProps = {
|
||||
onMount?: () => void;
|
||||
onUpdate?: (prevProps?: LifeCycleProps) => void;
|
||||
onUnmount?: () => void;
|
||||
data?: any;
|
||||
};
|
||||
export declare function LifeCycle(props: LifeCycleProps): any;
|
1
packages/horizon-router/router/@types/router/matcher/__tests__/lexer.test.d.ts
vendored
Normal file
1
packages/horizon-router/router/@types/router/matcher/__tests__/lexer.test.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export {};
|
1
packages/horizon-router/router/@types/router/matcher/__tests__/parser.test.d.ts
vendored
Normal file
1
packages/horizon-router/router/@types/router/matcher/__tests__/parser.test.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export {};
|
1
packages/horizon-router/router/@types/router/matcher/__tests__/utils.test.d.ts
vendored
Normal file
1
packages/horizon-router/router/@types/router/matcher/__tests__/utils.test.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export {};
|
|
@ -0,0 +1,2 @@
|
|||
import { Token } from './types';
|
||||
export declare function lexer(path: string): Token[];
|
|
@ -0,0 +1,18 @@
|
|||
import { GetURLParams, Parser, ParserOption } from './types';
|
||||
export type Params<P> = {
|
||||
[K in keyof P]?: P[K];
|
||||
};
|
||||
export type Matched<P = any> = {
|
||||
score: number[];
|
||||
params: Params<P>;
|
||||
path: string;
|
||||
url: string;
|
||||
isExact: boolean;
|
||||
};
|
||||
export declare function createPathParser<Str extends string>(pathname: Str, option?: ParserOption): Parser<GetURLParams<Str>>;
|
||||
export declare function createPathParser<P = unknown>(pathname: string, option?: ParserOption): Parser<P>;
|
||||
/**
|
||||
* @description 依次使用pathname与pattern进行匹配,根据匹配分数取得分数最高结果
|
||||
*/
|
||||
export declare function matchPath<P = any>(pathname: string, pattern: string | string[], option?: ParserOption): Matched<P> | null;
|
||||
export declare function generatePath<P = any>(path: string, params: Params<P>): string;
|
|
@ -0,0 +1,36 @@
|
|||
import { Matched, Params } from './parser';
|
||||
export type Token = {
|
||||
type: TokenType;
|
||||
value: string;
|
||||
};
|
||||
export declare enum TokenType {
|
||||
Delimiter = "delimiter",
|
||||
Static = "static",
|
||||
Param = "param",
|
||||
WildCard = "wildcard",
|
||||
LBracket = "(",
|
||||
RBracket = ")",
|
||||
Pattern = "pattern"
|
||||
}
|
||||
export interface Parser<P> {
|
||||
regexp: RegExp;
|
||||
keys: string[];
|
||||
parse(url: string): Matched<P> | null;
|
||||
compile(params: Params<P>): string;
|
||||
}
|
||||
export type ParserOption = {
|
||||
caseSensitive?: boolean;
|
||||
strictMode?: boolean;
|
||||
exact?: boolean;
|
||||
};
|
||||
type ClearLeading<U extends string> = U extends `/${infer R}` ? ClearLeading<R> : U;
|
||||
type ClearTailing<U extends string> = U extends `${infer L}/` ? ClearTailing<L> : U;
|
||||
type ParseParam<Param extends string> = Param extends `:${infer R}` ? {
|
||||
[K in R]: string;
|
||||
} : {};
|
||||
type MergeParams<OneParam extends Record<string, any>, OtherParam extends Record<string, any>> = {
|
||||
readonly [Key in keyof OneParam | keyof OtherParam]?: string;
|
||||
};
|
||||
type ParseURLString<Str extends string> = Str extends `${infer Param}/${infer Rest}` ? MergeParams<ParseParam<Param>, ParseURLString<ClearLeading<Rest>>> : ParseParam<Str>;
|
||||
export type GetURLParams<U extends string> = ParseURLString<ClearLeading<ClearTailing<U>>>;
|
||||
export {};
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* @description 将url中的//转换为/
|
||||
*/
|
||||
export declare function cleanPath(path: string): string;
|
||||
export declare function scoreCompare(score1: number[], score2: number[]): number;
|
||||
export declare function escapeStr(str: string): string;
|
|
@ -0,0 +1,3 @@
|
|||
import * as React from 'react';
|
||||
declare function withRouter<C extends React.ComponentType>(Component: C): (props: any) => JSX.Element;
|
||||
export default withRouter;
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"module": "./esm/connectRouter.js",
|
||||
"main": "./cjs/connectRouter.js",
|
||||
"types": "./@types/router/index2.d.ts",
|
||||
"peerDependencies": {
|
||||
"react-redux": "^6.0.0 || ^7.1.0",
|
||||
"redux": "^3.6.0 || ^4.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import { Action, Path } from '../history/types';
|
||||
|
||||
type Location = Partial<Path>
|
||||
|
||||
// 定义位置变化和history方法调用的Action type
|
||||
export enum ActionName {
|
||||
LOCATION_CHANGE = '$horizon-router/LOCATION_CHANGE',
|
||||
CALL_HISTORY_METHOD = '$horizon-router/CALL_HISTORY_METHOD'
|
||||
}
|
||||
|
||||
// 定义Action的两种数据类型
|
||||
export type ActionMessage = {
|
||||
type: ActionName.LOCATION_CHANGE
|
||||
payload: {
|
||||
location: Location,
|
||||
action: Action
|
||||
isFirstRendering: boolean
|
||||
}
|
||||
} | {
|
||||
type: ActionName.CALL_HISTORY_METHOD
|
||||
payload: {
|
||||
method: string,
|
||||
args: any
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const onLocationChanged = (location: Location, action: Action, isFirstRendering = false): ActionMessage => {
|
||||
return {
|
||||
type: ActionName.LOCATION_CHANGE,
|
||||
payload: {
|
||||
location,
|
||||
action,
|
||||
isFirstRendering,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const updateLocation = (method: string): (...args: any) => ActionMessage => {
|
||||
return (...args: any) => ({
|
||||
type: ActionName.CALL_HISTORY_METHOD,
|
||||
payload: {
|
||||
method,
|
||||
args,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const push = updateLocation('push');
|
||||
export const replace = updateLocation('replace');
|
||||
export const go = updateLocation('go');
|
|
@ -0,0 +1,128 @@
|
|||
import * as React from 'react';
|
||||
import { useLayoutEffect } from 'react';
|
||||
import { connect, ReactReduxContext } from 'react-redux';
|
||||
import { Store } from 'redux';
|
||||
import { reduxAdapter } from '@cloudsop/horizon';
|
||||
import { History, Location, Router } from '../router';
|
||||
import { Action, DefaultStateType, Navigation } from '../history/types';
|
||||
import { ActionMessage, onLocationChanged } from './actions';
|
||||
import stateReader from './reduxUtils';
|
||||
|
||||
type StoreType = 'HorizonXCompat' | 'Redux';
|
||||
|
||||
type ConnectedRouter<S> = {
|
||||
store: Store;
|
||||
history: History<S>;
|
||||
basename: string;
|
||||
children?: (() => React.ReactNode) | React.ReactNode;
|
||||
onLocationChanged: (location: Location<S>, action: Action, isFirstRendering: boolean) => ActionMessage;
|
||||
noInitialPop: boolean;
|
||||
omitRouter: boolean;
|
||||
storeType: StoreType;
|
||||
};
|
||||
|
||||
const { connect: hConnect } = reduxAdapter;
|
||||
|
||||
function ConnectedRouterWithoutMemo<S>(props: ConnectedRouter<S>) {
|
||||
const { store, history, onLocationChanged, omitRouter, children, storeType } = props;
|
||||
const { getLocation } = stateReader(storeType);
|
||||
|
||||
// 监听store变化
|
||||
const unsubscribe = store.subscribe(() => {
|
||||
// 获取redux State中的location信息
|
||||
const {
|
||||
pathname: pathnameInStore,
|
||||
search: searchInStore,
|
||||
hash: hashInStore,
|
||||
state: stateInStore,
|
||||
} = getLocation<S>(store.getState());
|
||||
|
||||
// 获取当前history对象中的location信息
|
||||
const {
|
||||
pathname: pathnameInHistory,
|
||||
search: searchInHistory,
|
||||
hash: hashInHistory,
|
||||
state: stateInHistory,
|
||||
} = history.location;
|
||||
|
||||
// 两个location不一致 执行跳转
|
||||
if (
|
||||
history.action === 'PUSH' &&
|
||||
(pathnameInHistory !== pathnameInStore ||
|
||||
searchInHistory !== searchInStore ||
|
||||
hashInHistory !== hashInStore ||
|
||||
stateInHistory !== stateInStore)
|
||||
) {
|
||||
history.push(
|
||||
{
|
||||
pathname: pathnameInStore,
|
||||
search: searchInStore,
|
||||
hash: hashInStore,
|
||||
},
|
||||
stateInStore,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const handleLocationChange = (args: Navigation<S>, isFirstRendering: boolean = false) => {
|
||||
const { location, action } = args;
|
||||
onLocationChanged(location, action, isFirstRendering);
|
||||
};
|
||||
|
||||
// 监听history更新
|
||||
const unListen = () => history.listen(handleLocationChange);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
return () => {
|
||||
unListen();
|
||||
unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!props.noInitialPop) {
|
||||
// 传递初始时位置信息,isFirstRendering设为true防止重复渲染
|
||||
handleLocationChange({ location: history.location, action: history.action }, true);
|
||||
}
|
||||
|
||||
if (omitRouter) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
let childrenNode: React.ReactNode;
|
||||
if (typeof children === 'function') {
|
||||
childrenNode = children();
|
||||
} else {
|
||||
childrenNode = children;
|
||||
}
|
||||
|
||||
return <Router history={history}>{childrenNode}</Router>;
|
||||
}
|
||||
|
||||
function getConnectedRouter<S = DefaultStateType>(type: StoreType) {
|
||||
const mapDispatchToProps = (dispatch: any) => ({
|
||||
onLocationChanged: (location: Location, action: Action, isFirstRendering: boolean) =>
|
||||
dispatch(onLocationChanged(location, action, isFirstRendering)),
|
||||
});
|
||||
const ConnectedRouter = React.memo(ConnectedRouterWithoutMemo<S>);
|
||||
|
||||
const ConnectedRouterWithContext = (props: any) => {
|
||||
const Context = props.context || ReactReduxContext;
|
||||
|
||||
return (
|
||||
<Context.Consumer>
|
||||
{({ store }: any) => <ConnectedRouter store={store} storeType={type} {...props} />}
|
||||
</Context.Consumer>
|
||||
);
|
||||
};
|
||||
|
||||
// 针对不同的Store类型,使用对应的connect函数
|
||||
if (type === 'HorizonXCompat') {
|
||||
return hConnect(null, mapDispatchToProps)(ConnectedRouterWithContext);
|
||||
}
|
||||
if (type === 'Redux') {
|
||||
return connect(null, mapDispatchToProps)(ConnectedRouterWithContext);
|
||||
} else {
|
||||
throw new Error('Invalid store type');
|
||||
}
|
||||
}
|
||||
|
||||
export { getConnectedRouter };
|
|
@ -0,0 +1,19 @@
|
|||
import { ActionMessage, ActionName } from './actions';
|
||||
import { History } from '../history/types';
|
||||
|
||||
// 定义connect-router对应的redux dispatch函数
|
||||
export function routerMiddleware(history: History) {
|
||||
return function(_: any) {
|
||||
return function(next: any) {
|
||||
return function(action: ActionMessage) {
|
||||
if (action.type !== ActionName.CALL_HISTORY_METHOD) {
|
||||
return next(action);
|
||||
}
|
||||
const { payload: { method, args } } = action;
|
||||
if (method in history) {
|
||||
(history as any)[method](...args);
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { createConnectRouter } from './reducer';
|
||||
|
||||
export { getConnectedRouter } from './connectedRouter';
|
||||
export const connectRouter = createConnectRouter();
|
||||
export { routerMiddleware } from './dispatch';
|
|
@ -0,0 +1,62 @@
|
|||
import { ActionName } from './actions';
|
||||
import { Action, History } from '../history/types';
|
||||
import { Location } from '../router';
|
||||
|
||||
type LocationWithQuery = Partial<Location> & { query?: Record<string, any> };
|
||||
|
||||
// 解析location对象,将其中的query参数解析并注入
|
||||
function injectQueryParams(location?: LocationWithQuery): LocationWithQuery {
|
||||
if (location && location.query) {
|
||||
return location;
|
||||
}
|
||||
|
||||
const queryString = location && location.search;
|
||||
|
||||
if (!queryString) {
|
||||
return {
|
||||
...location,
|
||||
query: {},
|
||||
};
|
||||
}
|
||||
const queryObject: Record<string, any> = {};
|
||||
|
||||
const params = new URLSearchParams(queryString);
|
||||
params.forEach((value, key) => (queryObject[key] = value));
|
||||
|
||||
return {
|
||||
...location,
|
||||
query: queryObject,
|
||||
};
|
||||
}
|
||||
|
||||
type InitRouterState = {
|
||||
location: LocationWithQuery;
|
||||
action: Action;
|
||||
};
|
||||
|
||||
type Payload = {
|
||||
type?: ActionName;
|
||||
payload?: any;
|
||||
};
|
||||
|
||||
export function createConnectRouter() {
|
||||
// 初始化redux State
|
||||
return (history: History) => {
|
||||
const initRouterState = {
|
||||
location: injectQueryParams(history.location),
|
||||
action: history.action,
|
||||
};
|
||||
|
||||
// 定义connect-router对应的redux reducer函数
|
||||
return (state: InitRouterState = initRouterState, { type, payload }: Payload = {}): any => {
|
||||
if (type === ActionName.LOCATION_CHANGE) {
|
||||
const { location, action, isFirstRendering } = payload;
|
||||
if (isFirstRendering) {
|
||||
return state;
|
||||
}
|
||||
return { ...state, location: injectQueryParams(location), action: action };
|
||||
}
|
||||
return state;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import { Location } from '../router';
|
||||
import { Action } from '../history/types';
|
||||
|
||||
// 获取redux state中的值
|
||||
export function getIn(state: Record<string, any>, path: string[]): any {
|
||||
if (!state) {
|
||||
return state;
|
||||
}
|
||||
const length = path.length;
|
||||
if (!length) {
|
||||
return undefined;
|
||||
}
|
||||
let res = state;
|
||||
for (let i = 0; i < length && !!res; ++i) {
|
||||
res = res[path[i]];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 从store的state中获取Router、Location、Action、Hash等信息
|
||||
const stateReader = (storeType: string) => {
|
||||
const isRouter = (value: unknown) => {
|
||||
return value !== null && typeof value === 'object' && !!getIn(value, ['location']) && !!getIn(value, ['action']);
|
||||
};
|
||||
|
||||
const getRouter = (state: any) => {
|
||||
const router = getIn(state, ['router']);
|
||||
if (!isRouter(router)) {
|
||||
throw new Error(`Could not find router reducer in ${storeType} store, it must be mounted under "router"`);
|
||||
}
|
||||
return router!;
|
||||
};
|
||||
|
||||
const getLocation = <S>(state: any): Partial<Location<S>> => getIn(getRouter(state), ['location']);
|
||||
const getAction = (state: any): Action => getIn(getRouter(state), ['action']);
|
||||
const getSearch = (state: any): string => getIn(getRouter(state), ['location', 'search']);
|
||||
const getHash = (state: any): string => getIn(getRouter(state), ['location', 'hash']);
|
||||
|
||||
return {
|
||||
getHash,
|
||||
getAction,
|
||||
getSearch,
|
||||
getRouter,
|
||||
getLocation,
|
||||
};
|
||||
};
|
||||
|
||||
export default stateReader
|
|
@ -0,0 +1,61 @@
|
|||
import { createPath } from '../utils';
|
||||
|
||||
describe('createPath', () => {
|
||||
describe('given only a pathname', () => {
|
||||
it('returns the pathname unchanged', () => {
|
||||
let path = createPath({ pathname: 'https://google.com' });
|
||||
expect(path).toBe('https://google.com');
|
||||
});
|
||||
});
|
||||
|
||||
describe('given a pathname and a search param', () => {
|
||||
it('returns the constructed pathname', () => {
|
||||
let path = createPath({
|
||||
pathname: 'https://google.com',
|
||||
search: '?something=cool',
|
||||
});
|
||||
expect(path).toBe('https://google.com?something=cool');
|
||||
});
|
||||
});
|
||||
|
||||
describe('given a pathname and a search param without ?', () => {
|
||||
it('returns the constructed pathname', () => {
|
||||
let path = createPath({
|
||||
pathname: 'https://google.com',
|
||||
search: 'something=cool',
|
||||
});
|
||||
expect(path).toBe('https://google.com?something=cool');
|
||||
});
|
||||
});
|
||||
|
||||
describe('given a pathname and a hash param', () => {
|
||||
it('returns the constructed pathname', () => {
|
||||
let path = createPath({
|
||||
pathname: 'https://google.com',
|
||||
hash: '#section-1',
|
||||
});
|
||||
expect(path).toBe('https://google.com#section-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('given a pathname and a hash param without #', () => {
|
||||
it('returns the constructed pathname', () => {
|
||||
let path = createPath({
|
||||
pathname: 'https://google.com',
|
||||
hash: 'section-1',
|
||||
});
|
||||
expect(path).toBe('https://google.com#section-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('given a full location object', () => {
|
||||
it('returns the constructed pathname', () => {
|
||||
let path = createPath({
|
||||
pathname: 'https://google.com',
|
||||
search: 'something=cool',
|
||||
hash: '#section-1',
|
||||
});
|
||||
expect(path).toBe('https://google.com?something=cool#section-1');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
import { HistoryProps, Listener, Navigation, Prompt } from './types';
|
||||
import transitionManager from './transitionManager';
|
||||
|
||||
// 抽取BrowserHistory和HashHistory中相同的方法
|
||||
export function getBaseHistory<S>(
|
||||
transitionManager: transitionManager<S>,
|
||||
setListener: (delta: number) => void,
|
||||
browserHistory: History,
|
||||
) {
|
||||
function go(step: number) {
|
||||
browserHistory.go(step);
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
browserHistory.go(-1);
|
||||
}
|
||||
|
||||
function goForward() {
|
||||
browserHistory.go(1);
|
||||
}
|
||||
|
||||
function listen(listener: Listener<S>): () => void {
|
||||
const cancel = transitionManager.addListener(listener);
|
||||
setListener(1);
|
||||
return () => {
|
||||
setListener(-1);
|
||||
cancel();
|
||||
};
|
||||
}
|
||||
|
||||
let isBlocked = false;
|
||||
|
||||
function block(prompt: Prompt<S> = false): () => void {
|
||||
const unblock = transitionManager.setPrompt(prompt);
|
||||
if (!isBlocked) {
|
||||
setListener(1);
|
||||
isBlocked = true;
|
||||
}
|
||||
return () => {
|
||||
if (isBlocked) {
|
||||
isBlocked = false;
|
||||
setListener(-1);
|
||||
}
|
||||
unblock();
|
||||
};
|
||||
}
|
||||
|
||||
function getUpdateStateFunc(historyProps: HistoryProps<S>) {
|
||||
return function (nextState: Navigation<S> | undefined) {
|
||||
if (nextState) {
|
||||
Object.assign(historyProps, nextState);
|
||||
}
|
||||
historyProps.length = browserHistory.length;
|
||||
const args = { location: historyProps.location, action: historyProps.action };
|
||||
transitionManager.notifyListeners(args);
|
||||
};
|
||||
}
|
||||
|
||||
return { go, goBack, goForward, listen, block, getUpdateStateFunc };
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
import { getDefaultConfirmation, isSupportHistory, isSupportsPopState } from './dom';
|
||||
import { Action, BaseOption, DefaultStateType, EventType, History, HistoryState, Location, Path, To } from './types';
|
||||
import { normalizeSlash, createMemoryRecord, createPath, createLocation, stripBasename } from './utils';
|
||||
import TransitionManager from './transitionManager';
|
||||
|
||||
import warning from './waring';
|
||||
import { getBaseHistory } from './baseHistory';
|
||||
|
||||
export type BrowserHistoryOption = {
|
||||
/**
|
||||
* forceRefresh为True时跳转时会强制刷新页面
|
||||
*/
|
||||
forceRefresh?: boolean;
|
||||
} & BaseOption;
|
||||
|
||||
export function createBrowserHistory<S = DefaultStateType>(options: BrowserHistoryOption = {}): History<S> {
|
||||
const supportHistory = isSupportHistory();
|
||||
const isSupportPopState = isSupportsPopState();
|
||||
const browserHistory = window.history;
|
||||
const { forceRefresh = false, getUserConfirmation = getDefaultConfirmation } = options;
|
||||
|
||||
const basename = options.basename ? normalizeSlash(options.basename) : '';
|
||||
|
||||
const initLocation = getLocation(getHistoryState());
|
||||
|
||||
const recordOperator = createMemoryRecord<string, Location<S>>(initLocation, l => l.key);
|
||||
|
||||
const transitionManager = new TransitionManager<S>();
|
||||
|
||||
const { go, goBack, goForward, listen, block, getUpdateStateFunc } = getBaseHistory<S>(
|
||||
transitionManager,
|
||||
setListener,
|
||||
browserHistory,
|
||||
);
|
||||
|
||||
const history: History<S> = {
|
||||
action: Action.pop,
|
||||
length: browserHistory.length,
|
||||
location: initLocation,
|
||||
go,
|
||||
goBack,
|
||||
goForward,
|
||||
listen,
|
||||
block,
|
||||
push,
|
||||
replace,
|
||||
createHref,
|
||||
};
|
||||
|
||||
const updateState = getUpdateStateFunc(history);
|
||||
|
||||
function getHistoryState() {
|
||||
return supportHistory ? window.history.state : {};
|
||||
}
|
||||
|
||||
function getLocation(historyState: Partial<HistoryState<S>>) {
|
||||
const { search, hash } = window.location;
|
||||
const { key, state } = historyState || {};
|
||||
let pathname = window.location.pathname;
|
||||
pathname = basename ? stripBasename(pathname, basename) : pathname;
|
||||
|
||||
return createLocation<S>('', { pathname, search, hash }, state, key);
|
||||
}
|
||||
|
||||
// 拦截页面POP事件后,防止返回到的页面被重复拦截
|
||||
let forceJump = false;
|
||||
|
||||
function handlePopState(location: Location<S>) {
|
||||
if (forceJump) {
|
||||
forceJump = false;
|
||||
updateState(undefined);
|
||||
} else {
|
||||
const action = Action.pop;
|
||||
|
||||
const callback = (isJump: boolean) => {
|
||||
if (isJump) {
|
||||
// 执行跳转行为
|
||||
updateState({ action: action, location: location });
|
||||
} else {
|
||||
revertPopState(location, history.location);
|
||||
}
|
||||
};
|
||||
|
||||
transitionManager.confirmJumpTo(location, action, getUserConfirmation, callback);
|
||||
}
|
||||
}
|
||||
|
||||
function popStateListener(event: PopStateEvent) {
|
||||
handlePopState(getLocation(event.state));
|
||||
}
|
||||
|
||||
function hashChangeListener() {
|
||||
const location = getLocation(getHistoryState());
|
||||
handlePopState(location);
|
||||
}
|
||||
|
||||
let listenerCount = 0;
|
||||
|
||||
function setListener(count: number) {
|
||||
listenerCount += count;
|
||||
if (listenerCount === 1 && count === 1) {
|
||||
window.addEventListener(EventType.PopState, popStateListener);
|
||||
if (!isSupportPopState) {
|
||||
window.addEventListener(EventType.HashChange, hashChangeListener);
|
||||
}
|
||||
} else if (listenerCount === 0) {
|
||||
window.removeEventListener(EventType.PopState, popStateListener);
|
||||
if (!isSupportPopState) {
|
||||
window.removeEventListener(EventType.HashChange, hashChangeListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 取消页面跳转并恢复到跳转前的页面
|
||||
function revertPopState(form: Location<S>, to: Location<S>) {
|
||||
const delta = recordOperator.getDelta(to, form);
|
||||
if (delta !== 0) {
|
||||
go(delta);
|
||||
forceJump = true;
|
||||
}
|
||||
}
|
||||
|
||||
function createHref(path: Partial<Path>) {
|
||||
return basename + createPath(path);
|
||||
}
|
||||
|
||||
function push(to: To, state?: S) {
|
||||
const action = Action.push;
|
||||
const location = createLocation<S>(history.location, to, state, undefined);
|
||||
|
||||
transitionManager.confirmJumpTo(location, action, getUserConfirmation, isJump => {
|
||||
if (!isJump) {
|
||||
return;
|
||||
}
|
||||
const href = createHref(location);
|
||||
const { key, state } = location;
|
||||
|
||||
if (supportHistory) {
|
||||
if (forceRefresh) {
|
||||
window.location.href = href;
|
||||
} else {
|
||||
browserHistory.pushState({ key: key, state: state }, '', href);
|
||||
recordOperator.addRecord(history.location, location, action);
|
||||
updateState({ action, location });
|
||||
}
|
||||
} else {
|
||||
warning(state !== undefined, 'Browser history cannot push state in browsers that do not support HTML5 history');
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function replace(to: To, state?: S) {
|
||||
const action = Action.replace;
|
||||
const location = createLocation<S>(history.location, to, state, undefined);
|
||||
|
||||
transitionManager.confirmJumpTo(location, action, getUserConfirmation, isJump => {
|
||||
if (!isJump) {
|
||||
return;
|
||||
}
|
||||
const href = createHref(location);
|
||||
const { key, state } = location;
|
||||
if (supportHistory) {
|
||||
if (forceRefresh) {
|
||||
window.location.replace(href);
|
||||
} else {
|
||||
browserHistory.replaceState({ key: key, state: state }, '', href);
|
||||
recordOperator.addRecord(history.location, location, action);
|
||||
updateState({ action, location });
|
||||
}
|
||||
} else {
|
||||
warning(state !== undefined, 'Browser history cannot push state in browsers that do not support HTML5 history');
|
||||
window.location.replace(href);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return history;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
export function isBrowser(): boolean {
|
||||
return typeof window !== 'undefined' && window.document && typeof window.document.createElement === 'function';
|
||||
}
|
||||
|
||||
export function getDefaultConfirmation(message: string, callBack: (result: boolean) => void) {
|
||||
callBack(window.confirm(message));
|
||||
}
|
||||
|
||||
// 判断浏览器是否支持pushState方法,pushState是browserHistory实现的基础
|
||||
export function isSupportHistory(): boolean {
|
||||
return isBrowser() && window.history && 'pushState' in window.history;
|
||||
}
|
||||
|
||||
// 判断浏览器是否支持PopState事件
|
||||
export function isSupportsPopState(): boolean {
|
||||
return window.navigator.userAgent.indexOf('Trident') === -1;
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
import { Action, BaseOption, DefaultStateType, EventType, History, Location, To } from './types';
|
||||
import {
|
||||
addHeadSlash,
|
||||
normalizeSlash,
|
||||
createMemoryRecord,
|
||||
createPath,
|
||||
createLocation,
|
||||
isLocationEqual,
|
||||
stripBasename,
|
||||
stripHeadSlash,
|
||||
} from './utils';
|
||||
import { getDefaultConfirmation } from './dom';
|
||||
import TransitionManager from './transitionManager';
|
||||
|
||||
import warning from './waring';
|
||||
import { getBaseHistory } from './baseHistory';
|
||||
|
||||
export type urlHashType = 'slash' | 'noslash';
|
||||
|
||||
type HashHistoryOption = {
|
||||
hashType?: urlHashType;
|
||||
} & BaseOption;
|
||||
|
||||
// 获取#前的内容
|
||||
function stripHash(path: string): string {
|
||||
const idx = path.indexOf('#');
|
||||
return idx === -1 ? path : path.substring(0, idx);
|
||||
}
|
||||
|
||||
// 获取#后的内容
|
||||
function getHashContent(path: string): string {
|
||||
const idx = path.indexOf('#');
|
||||
return idx === -1 ? '' : path.substring(idx + 1);
|
||||
}
|
||||
|
||||
export function createHashHistory<S = DefaultStateType>(option: HashHistoryOption = {}): History<S> {
|
||||
const browserHistory = window.history;
|
||||
const { hashType = 'slash', getUserConfirmation = getDefaultConfirmation } = option;
|
||||
|
||||
const basename = option.basename ? normalizeSlash(option.basename) : '';
|
||||
|
||||
const pathDecoder = addHeadSlash;
|
||||
const pathEncoder = hashType === 'slash' ? addHeadSlash : stripHeadSlash;
|
||||
|
||||
function getLocation() {
|
||||
let hashPath = pathDecoder(getHashContent(window.location.hash));
|
||||
if (basename) {
|
||||
hashPath = stripBasename(hashPath, basename);
|
||||
}
|
||||
|
||||
return createLocation<S>('', hashPath, undefined, 'default');
|
||||
}
|
||||
|
||||
const initLocation = getLocation();
|
||||
|
||||
const memRecords = createMemoryRecord<string, Location<S>>(initLocation, createPath);
|
||||
|
||||
const transitionManager = new TransitionManager<S>();
|
||||
|
||||
function createHref(location: Location<S>) {
|
||||
const tag = document.querySelector('base');
|
||||
const base = tag && tag.getAttribute('href') ? stripHash(window.location.href) : '';
|
||||
return base + '#' + pathEncoder(basename + createPath(location));
|
||||
}
|
||||
|
||||
let forceNextPop = false;
|
||||
let ignorePath: null | string = null;
|
||||
|
||||
const { go, goBack, goForward, listen, block, getUpdateStateFunc } = getBaseHistory(
|
||||
transitionManager,
|
||||
setListener,
|
||||
browserHistory,
|
||||
);
|
||||
|
||||
const history: History<S> = {
|
||||
action: Action.pop,
|
||||
length: browserHistory.length,
|
||||
location: initLocation,
|
||||
go,
|
||||
goBack,
|
||||
goForward,
|
||||
push,
|
||||
replace,
|
||||
listen,
|
||||
block,
|
||||
createHref,
|
||||
};
|
||||
|
||||
const updateState = getUpdateStateFunc(history);
|
||||
|
||||
function push(to: To, state?: S) {
|
||||
warning(state !== undefined, 'Hash history does not support state, it will be ignored');
|
||||
|
||||
const action = Action.push;
|
||||
const location = createLocation<S>(history.location, to, undefined, '');
|
||||
|
||||
transitionManager.confirmJumpTo(location, action, getUserConfirmation, isJump => {
|
||||
if (!isJump) {
|
||||
return;
|
||||
}
|
||||
const path = createPath(location);
|
||||
const encodedPath = pathEncoder(basename + path);
|
||||
// 前后hash不一样才进行跳转
|
||||
if (getHashContent(window.location.href) !== encodedPath) {
|
||||
ignorePath = encodedPath;
|
||||
window.location.hash = encodedPath;
|
||||
|
||||
memRecords.addRecord(history.location, location, action);
|
||||
|
||||
updateState({ action, location });
|
||||
} else {
|
||||
updateState(undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function replace(to: To, state?: S) {
|
||||
warning(state !== undefined, 'Hash history does not support state, it will be ignored');
|
||||
const action = Action.replace;
|
||||
const location = createLocation<S>(history.location, to, undefined, '');
|
||||
|
||||
transitionManager.confirmJumpTo(location, action, getUserConfirmation, isJump => {
|
||||
if (!isJump) {
|
||||
return;
|
||||
}
|
||||
const path = createPath(location);
|
||||
const encodedPath = pathEncoder(basename + path);
|
||||
if (getHashContent(window.location.href) !== encodedPath) {
|
||||
ignorePath = path;
|
||||
window.location.replace(stripHash(window.location.href) + '#' + encodedPath);
|
||||
}
|
||||
memRecords.addRecord(history.location, location, action);
|
||||
updateState({ action, location });
|
||||
});
|
||||
}
|
||||
|
||||
function handleHashChange() {
|
||||
const hashPath = getHashContent(window.location.href);
|
||||
const encodedPath = pathEncoder(hashPath);
|
||||
if (hashPath !== encodedPath) {
|
||||
window.location.replace(stripHash(window.location.href) + '#' + encodedPath);
|
||||
} else {
|
||||
const location = getLocation();
|
||||
const prevLocation = history.location;
|
||||
if (!forceNextPop && isLocationEqual(location, prevLocation)) {
|
||||
return;
|
||||
}
|
||||
if (ignorePath === createPath(location)) {
|
||||
return;
|
||||
}
|
||||
ignorePath = null;
|
||||
handlePopState(location);
|
||||
}
|
||||
}
|
||||
|
||||
function handlePopState(location: Location<S>) {
|
||||
if (forceNextPop) {
|
||||
forceNextPop = false;
|
||||
updateState(undefined);
|
||||
} else {
|
||||
const action = Action.pop;
|
||||
|
||||
const callback = (isJump: boolean) => {
|
||||
if (isJump) {
|
||||
updateState({ action: action, location: location });
|
||||
} else {
|
||||
revertPopState(location);
|
||||
}
|
||||
};
|
||||
|
||||
transitionManager.confirmJumpTo(location, action, getUserConfirmation, callback);
|
||||
}
|
||||
}
|
||||
|
||||
// 在跳转行为被Block后,用History.go()跳转回之前的页面
|
||||
function revertPopState(form: Location<S>) {
|
||||
const to = history.location;
|
||||
const delta = memRecords.getDelta(to, form);
|
||||
if (delta !== 0) {
|
||||
go(delta);
|
||||
forceNextPop = true;
|
||||
}
|
||||
}
|
||||
|
||||
let listenerCount = 0;
|
||||
|
||||
function setListener(delta: number) {
|
||||
listenerCount += delta;
|
||||
if (listenerCount === 1 && delta === 1) {
|
||||
window.addEventListener(EventType.HashChange, handleHashChange);
|
||||
} else if (listenerCount === 0) {
|
||||
window.removeEventListener(EventType.HashChange, handleHashChange);
|
||||
}
|
||||
}
|
||||
|
||||
return history;
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import { Action, CallBackFunc, ConfirmationFunc, Listener, Location, Navigation, Prompt, TManager } from './types';
|
||||
|
||||
class TransitionManager<S> implements TManager<S> {
|
||||
private prompt: Prompt<S>;
|
||||
private listeners: Listener<S>[];
|
||||
|
||||
constructor() {
|
||||
this.prompt = null;
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
public setPrompt(prompt: Prompt<S>): () => void {
|
||||
this.prompt = prompt;
|
||||
|
||||
// 清除Prompt
|
||||
return () => {
|
||||
if (this.prompt === prompt) {
|
||||
this.prompt = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 使用发布订阅模式管理history的监听者
|
||||
public addListener(func: Listener<S>): () => void {
|
||||
let isActive = true;
|
||||
const listener = (args: Navigation<S>) => {
|
||||
if (isActive) {
|
||||
func(args);
|
||||
}
|
||||
};
|
||||
this.listeners.push(listener);
|
||||
return () => {
|
||||
isActive = false;
|
||||
// 移除对应的监听者
|
||||
this.listeners = this.listeners.filter(item => item !== listener);
|
||||
};
|
||||
}
|
||||
|
||||
public notifyListeners(args: Navigation<S>) {
|
||||
for (const listener of this.listeners) {
|
||||
listener(args);
|
||||
}
|
||||
}
|
||||
|
||||
public confirmJumpTo(
|
||||
location: Location<S>,
|
||||
action: Action,
|
||||
userConfirmationFunc: ConfirmationFunc,
|
||||
callBack: CallBackFunc
|
||||
) {
|
||||
if (this.prompt !== null) {
|
||||
const result = typeof this.prompt === 'function' ? this.prompt(location, action) : this.prompt;
|
||||
if (typeof result === 'string') {
|
||||
typeof userConfirmationFunc === 'function' ? userConfirmationFunc(result, callBack) : callBack(true);
|
||||
} else {
|
||||
callBack(result !== false);
|
||||
}
|
||||
} else {
|
||||
callBack(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TransitionManager;
|
|
@ -0,0 +1,92 @@
|
|||
export type BaseOption = {
|
||||
basename?: string;
|
||||
getUserConfirmation?: ConfirmationFunc;
|
||||
};
|
||||
|
||||
export interface HistoryProps<T = unknown> {
|
||||
readonly action: Action;
|
||||
|
||||
readonly location: Location<T>;
|
||||
|
||||
length: number;
|
||||
}
|
||||
|
||||
export interface History<T = unknown> extends HistoryProps<T> {
|
||||
createHref(path: Partial<Path>): string;
|
||||
|
||||
push(to: To, state?: T): void;
|
||||
|
||||
replace(to: To, state?: T): void;
|
||||
|
||||
listen(listener: Listener<T>): () => void;
|
||||
|
||||
block(prompt: Prompt<T>): () => void;
|
||||
|
||||
go(index: number): void;
|
||||
|
||||
goBack(): void;
|
||||
|
||||
goForward(): void;
|
||||
}
|
||||
|
||||
export enum Action {
|
||||
pop = 'POP',
|
||||
push = 'PUSH',
|
||||
replace = 'REPLACE',
|
||||
}
|
||||
|
||||
export enum EventType {
|
||||
PopState = 'popstate',
|
||||
HashChange = 'hashchange',
|
||||
}
|
||||
|
||||
export type Path = {
|
||||
pathname: string;
|
||||
|
||||
search: string;
|
||||
|
||||
hash: string;
|
||||
};
|
||||
|
||||
export type HistoryState<T> = {
|
||||
state?: T;
|
||||
|
||||
key: string;
|
||||
};
|
||||
|
||||
export type DefaultStateType = unknown;
|
||||
|
||||
export type Location<T = unknown> = Path & HistoryState<T>;
|
||||
|
||||
export type To = string | Partial<Path>;
|
||||
|
||||
export interface Listener<T = unknown> {
|
||||
(navigation: Navigation<T>): void;
|
||||
}
|
||||
|
||||
export interface Navigation<T = unknown> {
|
||||
action: Action;
|
||||
|
||||
location: Location<T>;
|
||||
}
|
||||
|
||||
export type Prompt<S> = string | boolean | null | ((location: Location<S>, action: Action) => void);
|
||||
|
||||
export type CallBackFunc = (isJump: boolean) => void;
|
||||
|
||||
export type ConfirmationFunc = (message: string, callBack: CallBackFunc) => void;
|
||||
|
||||
export interface TManager<S> {
|
||||
setPrompt(next: Prompt<S>): () => void;
|
||||
|
||||
addListener(func: (navigation: Navigation<S>) => void): () => void;
|
||||
|
||||
notifyListeners(args: Navigation<S>): void;
|
||||
|
||||
confirmJumpTo(
|
||||
location: Location<S>,
|
||||
action: Action,
|
||||
userConfirmationFunc: ConfirmationFunc,
|
||||
callBack: CallBackFunc,
|
||||
): void;
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
import { Action, Location, Path, To } from './types';
|
||||
|
||||
export function createPath(path: Partial<Path>): string {
|
||||
const { search, hash } = path;
|
||||
let pathname = path.pathname || '/';
|
||||
if (search && search !== '?') {
|
||||
pathname += search.startsWith('?') ? search : '?' + search;
|
||||
}
|
||||
if (hash && hash !== '#') {
|
||||
pathname += hash.startsWith('#') ? hash : '#' + hash;
|
||||
}
|
||||
return pathname;
|
||||
}
|
||||
|
||||
export function parsePath(url: string): Partial<Path> {
|
||||
if (!url) {
|
||||
return {};
|
||||
}
|
||||
let parsedPath: Partial<Path> = {};
|
||||
|
||||
let hashIdx = url.indexOf('#');
|
||||
if (hashIdx > -1) {
|
||||
parsedPath.hash = url.substring(hashIdx);
|
||||
url = url.substring(0, hashIdx);
|
||||
}
|
||||
|
||||
let searchIdx = url.indexOf('?');
|
||||
if (searchIdx > -1) {
|
||||
parsedPath.search = url.substring(searchIdx);
|
||||
url = url.substring(0, searchIdx);
|
||||
}
|
||||
if (url) {
|
||||
parsedPath.pathname = url;
|
||||
}
|
||||
return parsedPath;
|
||||
}
|
||||
|
||||
export function createLocation<S>(current: string | Location, to: To, state?: S, key?: string): Readonly<Location<S>> {
|
||||
let pathname = typeof current === 'string' ? current : current.pathname;
|
||||
let urlObj = typeof to === 'string' ? parsePath(to) : to;
|
||||
// 随机key长度取6
|
||||
const getRandKey = genRandomKey(6);
|
||||
const location = {
|
||||
pathname: pathname,
|
||||
search: '',
|
||||
hash: '',
|
||||
state: state,
|
||||
key: typeof key === 'string' ? key : getRandKey(),
|
||||
...urlObj,
|
||||
};
|
||||
if (!location.pathname) {
|
||||
location.pathname = '/';
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
export function isLocationEqual(p1: Partial<Path>, p2: Partial<Path>) {
|
||||
return p1.pathname === p2.pathname && p1.search === p2.search && p1.hash === p2.hash;
|
||||
}
|
||||
|
||||
export function addHeadSlash(path: string): string {
|
||||
if (path[0] === '/') {
|
||||
return path;
|
||||
}
|
||||
return '/' + path;
|
||||
}
|
||||
|
||||
export function stripHeadSlash(path: string): string {
|
||||
if (path[0] === '/') {
|
||||
return path.substring(1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
export function normalizeSlash(path: string): string {
|
||||
const tempPath = addHeadSlash(path);
|
||||
if (tempPath[tempPath.length - 1] === '/') {
|
||||
return tempPath.substring(0, tempPath.length - 1);
|
||||
}
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
export function hasBasename(path: string, prefix: string): Boolean {
|
||||
return (
|
||||
path.toLowerCase().indexOf(prefix.toLowerCase()) === 0 && ['/', '?', '#', ''].includes(path.charAt(prefix.length))
|
||||
);
|
||||
}
|
||||
|
||||
export function stripBasename(path: string, prefix: string): string {
|
||||
return hasBasename(path, prefix) ? path.substring(prefix.length) : path;
|
||||
}
|
||||
|
||||
// 使用随机生成的Key记录被访问过的URL,当Block被被触发时利用delta值跳转到之前的页面
|
||||
export function createMemoryRecord<T, S>(initVal: S, fn: (arg: S) => T) {
|
||||
let visitedRecord: T[] = [fn(initVal)];
|
||||
|
||||
function getDelta(to: S, form: S): number {
|
||||
let toIdx = visitedRecord.lastIndexOf(fn(to));
|
||||
if (toIdx === -1) {
|
||||
toIdx = 0;
|
||||
}
|
||||
let fromIdx = visitedRecord.lastIndexOf(fn(form));
|
||||
if (fromIdx === -1) {
|
||||
fromIdx = 0;
|
||||
}
|
||||
return toIdx - fromIdx;
|
||||
}
|
||||
|
||||
function addRecord(current: S, newRecord: S, action: Action) {
|
||||
const curVal = fn(current);
|
||||
const NewVal = fn(newRecord);
|
||||
if (action === Action.push) {
|
||||
const prevIdx = visitedRecord.lastIndexOf(curVal);
|
||||
const newVisitedRecord = visitedRecord.slice(0, prevIdx + 1);
|
||||
newVisitedRecord.push(NewVal);
|
||||
visitedRecord = newVisitedRecord;
|
||||
}
|
||||
if (action === Action.replace) {
|
||||
const prevIdx = visitedRecord.lastIndexOf(curVal);
|
||||
if (prevIdx !== -1) {
|
||||
visitedRecord[prevIdx] = NewVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { getDelta, addRecord };
|
||||
}
|
||||
|
||||
function genRandomKey(length: number): () => string {
|
||||
const end = length + 2;
|
||||
return () => {
|
||||
return Math.random().toString(18).substring(2, end);
|
||||
};
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
function warning(condition: any, message: string) {
|
||||
if (condition) {
|
||||
if (console && typeof console.warn === 'function') {
|
||||
console.warn(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default warning;
|
|
@ -0,0 +1,32 @@
|
|||
import * as React from 'react';
|
||||
import { useRef, ReactNode } from 'react';
|
||||
import Router from './Router';
|
||||
import { createBrowserHistory } from '../history/browerHistory';
|
||||
import { ConfirmationFunc, History } from '../history/types';
|
||||
|
||||
export type BaseRouterProps = {
|
||||
basename: string;
|
||||
getUserConfirmation: ConfirmationFunc;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export type BrowserRouterProps = BaseRouterProps & {
|
||||
forceRefresh: boolean;
|
||||
};
|
||||
|
||||
function BrowserRouter<P extends Partial<BrowserRouterProps>>(props: P) {
|
||||
// 使用Ref持有History对象,防止重复渲染
|
||||
let historyRef = useRef<History>();
|
||||
|
||||
if (historyRef.current === null || historyRef.current === undefined) {
|
||||
historyRef.current = createBrowserHistory({
|
||||
basename: props.basename,
|
||||
forceRefresh: props.forceRefresh,
|
||||
getUserConfirmation: props.getUserConfirmation,
|
||||
});
|
||||
}
|
||||
|
||||
return <Router history={historyRef.current}>{props.children}</Router>;
|
||||
}
|
||||
|
||||
export default BrowserRouter;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue