inula-router问题修复及优化

This commit is contained in:
huangxuan 2023-12-01 15:43:36 +08:00
parent 1af8f0271a
commit 1f0dbaa180
No known key found for this signature in database
GPG Key ID: E79F50C67022565D
8 changed files with 58 additions and 27 deletions

View File

@ -16,7 +16,7 @@
import Inula from 'openinula';
import { useLayoutEffect, useRef, reduxAdapter, InulaNode } from 'openinula';
import { connect, ReactReduxContext } from 'react-redux';
import { Store } from 'redux';
import type { Store } from 'redux';
import { History, Location, Router } from '../router';
import { Action, DefaultStateType, Navigation } from '../history/types';
import { ActionMessage, onLocationChanged } from './actions';
@ -130,9 +130,14 @@ function getConnectedRouter(type: StoreType) {
);
};
const ConnectedHRouterWithContext = (props: any) => {
const { store, ...rest } = props;
return <ConnectedRouter store={store} storeType={type} {...rest} />;
};
// 针对不同的Store类型使用对应的connect函数
if (type === 'InulaXCompat') {
return hConnect(null as any, mapDispatchToProps)(ConnectedRouterWithContext as any);
return hConnect(null as any, mapDispatchToProps)(ConnectedHRouterWithContext as any);
}
if (type === 'Redux') {
return connect(null, mapDispatchToProps)(ConnectedRouterWithContext);

View File

@ -107,7 +107,7 @@ export function createHashHistory<S = DefaultStateType>(option: HashHistoryOptio
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, '');
const location = createLocation<S>(history.location, to, state, '');
transitionManager.confirmJumpTo(location, action, getUserConfirmation, isJump => {
if (!isJump) {
@ -132,7 +132,7 @@ export function createHashHistory<S = DefaultStateType>(option: HashHistoryOptio
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, '');
const location = createLocation<S>(history.location, to, state, '');
transitionManager.confirmJumpTo(location, action, getUserConfirmation, isJump => {
if (!isJump) {

View File

@ -28,8 +28,8 @@ export function createPath(path: Partial<Path>): string {
}
export function parsePath(url: string): Partial<Path> {
let pathname = url || '/';
const parsedPath: Partial<Path> = {
pathname: url || '/',
search: '',
hash: '',
};
@ -38,16 +38,16 @@ export function parsePath(url: string): Partial<Path> {
if (hashIdx > -1) {
const hash = url.substring(hashIdx);
parsedPath.hash = hash === '#' ? '' : hash;
url = url.substring(0, hashIdx);
pathname = pathname.substring(0, hashIdx);
}
const searchIdx = url.indexOf('?');
if (searchIdx > -1) {
const search = url.substring(searchIdx);
parsedPath.search = search === '?' ? '' : search;
url = url.substring(0, searchIdx);
pathname = pathname.substring(0, searchIdx);
}
parsedPath.pathname = url;
parsedPath.pathname = pathname;
return parsedPath;
}
@ -116,12 +116,12 @@ export function stripBasename(path: string, prefix: string): string {
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));
function getDelta(toKey: S, formKey: S): number {
let toIdx = visitedRecord.lastIndexOf(fn(toKey));
if (toIdx === -1) {
toIdx = 0;
}
let fromIdx = visitedRecord.lastIndexOf(fn(form));
let fromIdx = visitedRecord.lastIndexOf(fn(formKey));
if (fromIdx === -1) {
fromIdx = 0;
}

View File

@ -24,27 +24,42 @@ import { parsePath } from '../history/utils';
type NavLinkProps = {
to: Partial<Location> | string | ((location: Location) => string | Partial<Location>);
isActive?: (match: Matched | null, location: Location) => boolean;
isActive?<P extends { [K in keyof P]?: string }>(match: Matched<P> | null, location: Location): boolean;
exact?: boolean;
strict?: boolean;
sensitive?: boolean;
className?: string | ((isActive: boolean) => string);
activeClassName?: string;
[key: string]: any;
} & LinkProps;
} & Omit<LinkProps, 'className'>;
type Page = 'page';
function NavLink<P extends NavLinkProps>(props: P) {
const { to, isActive, ...rest } = props;
const { to, isActive, exact, strict, sensitive, className, activeClassName, ...rest } = props;
const context = useContext(Context);
const toLocation = typeof to === 'function' ? to(context.location) : to;
const { pathname } = typeof toLocation === 'string' ? parsePath(toLocation) : toLocation;
const match = pathname ? matchPath(context.location.pathname, pathname) : null;
const match = pathname ? matchPath(context.location.pathname, pathname, {
exact: exact,
strictMode: strict,
caseSensitive: sensitive,
}) : null;
const isLinkActive = match && isActive ? isActive(match, context.location) : false;
const isLinkActive = !!(isActive ? isActive(match, context.location) : match);
let classNames = typeof className === 'function' ? className(isLinkActive) : className;
if (isLinkActive) {
classNames = [activeClassName, classNames].filter(Boolean).join('');
}
const page: Page = 'page';
const otherProps = {
'aria-current': isLinkActive ? page : false,
className: classNames,
'aria-current': isLinkActive ? page : undefined,
...rest,
};

View File

@ -43,15 +43,19 @@ export type RouteProps<P extends Record<string, any> = {}, Path extends string =
function Route<Path extends string, P extends Record<string, any> = GetURLParams<Path>>(props: RouteProps<P, Path>) {
const context = useContext(RouterContext);
const { computed, location, path } = props;
let { children, component, render } = props;
const { computed, location, path, component, render, strict, sensitive, exact } = props;
let { children } = props;
let match: Matched<P> | null;
const routeLocation = location || context.location;
if (computed) {
match = computed;
} else if (path) {
match = matchPath<P>(routeLocation.pathname, path);
match = matchPath<P>(routeLocation.pathname, path, {
strictMode: strict,
caseSensitive: sensitive,
exact: exact,
});
} else {
match = context.match;
}

View File

@ -29,22 +29,27 @@ function Router<P extends RouterProps>(props: P) {
const { history, children = null } = props;
const [location, setLocation] = useState(props.history.location);
const pendingLocation = useRef<Location | null>(null);
const unListen = useRef<null | (() => void)>(null);
const isMount = useRef<boolean>(false);
// 在Router加载时就监听history地址变化以保证在始渲染时重定向能正确触发
const unListen = useRef<null | (() => void)>(
history.listen(arg => {
if (unListen.current === null) {
unListen.current = history.listen(arg => {
pendingLocation.current = arg.location;
}),
);
});
}
// 模拟componentDidMount和componentWillUnmount
useLayoutEffect(() => {
isMount.current = true;
if (unListen.current) {
unListen.current();
}
// 监听history中的位置变化
unListen.current = history.listen(arg => {
setLocation(arg.location);
if (isMount.current) {
setLocation(arg.location);
}
});
if (pendingLocation.current) {
@ -53,6 +58,7 @@ function Router<P extends RouterProps>(props: P) {
return () => {
if (unListen.current) {
isMount.current = false;
unListen.current();
unListen.current = null;
pendingLocation.current = null;

View File

@ -40,7 +40,7 @@ export type Matched<P = any> = {
const defaultOption: Required<ParserOption> = {
// url匹配时是否大小写敏感
caseSensitive: true,
caseSensitive: false,
// 是否严格匹配url结尾的/
strictMode: false,
// 是否完全精确匹配

View File

@ -20,10 +20,11 @@ import RouterContext from './context';
function withRouter<C extends ComponentType>(Component: C) {
function ComponentWithRouterProp(props: any) {
const { wrappedComponentRef, ...rest } = props;
const { history, location, match } = useContext(RouterContext);
const routeProps = { history: history, location: location, match: match };
return <Component {...props} {...routeProps} />;
return <Component {...routeProps} {...rest} ref={wrappedComponentRef} />;
}
return ComponentWithRouterProp;