react-router 源码阅读
这次的版本是 6.2.1
使用
相比较 5.x 版本,
简单的 v6 例子:
function App(){
return <BrowserRouter>
<Routes>
<Route path="/about" element={<About/>}/>
<Route path="/users" element={<Users/>}/>
<Route path="/" element={<Home/>}/>
</Routes>
</BrowserRouter>
}
context
在 react-router 中, 他创建了两个 context 供后续的使用, 当然这两个 context 是在内部的, 并没有 API 暴露出来
NavigationContext
/**
* 一个路由对象的基本构成
*/
export interface RouteObject {
caseSensitive?: boolean;
children?: RouteObject[];
element?: React.ReactNode;
index?: boolean;
path?: string;
}
// 常用的参数类型
export type Params<Key extends string = string> = {
readonly [key in Key]: string | undefined;
};
/**
* 一个 路由匹配 接口
*/
export interface RouteMatch<ParamKey extends string = string> {
/**
* 动态参数的名称和值的URL
*/
params: Params<ParamKey>;
/**
* 路径名
*/
pathname: string;
/**
* 之前匹配的路径名
*/
pathnameBase: string;
/**
* 匹配到的路由对象
*/
route: RouteObject;
}
interface RouteContextObject {
outlet: React.ReactElement | null;
matches: RouteMatch[];
}
const RouteContext = React.createContext<RouteContextObject>({
outlet: null,
matches: []
});
LocationContext
import type {
Location,
Action as NavigationType
} from "history";
interface LocationContextObject {
location: Location; // 原生的 location 对象, window.location
/**
* enum Action 一个枚举, 他有三个参数, 代表路由三种动作
* Pop = "POP",
* Push = "PUSH",
* Replace = "REPLACE"
*/
navigationType: NavigationType;
}
const LocationContext = React.createContext<LocationContextObject>(null!);
MemoryRouter
在 react-router-dom
的源码解析中我们说到了 BrowserRouter
和 HashRouter
, 那么这个 MemoryRouter
又是什么呢
他是将 URL 的历史记录保存在内存中的
他的源码和其他两个 Router 最大的区别就是一个 createMemoryHistory
方法, 此方法也来自于 history
库中
export function MemoryRouter({
basename,
children,
initialEntries,
initialIndex
}: MemoryRouterProps): React.ReactElement {
let historyRef = React.useRef<MemoryHistory>();
if (historyRef.current == null) {
historyRef.current = createMemoryHistory({ initialEntries, initialIndex });
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
那我们现在来看一看这个方法, 这里只讲他与 createHashHistory
不同的地方:
export function createMemoryHistory(
options: MemoryHistoryOptions = {}
): MemoryHistory {
let { initialEntries = ['/'], initialIndex } = options; // 不同的初始值 initialEntries
let entries: Location[] = initialEntries.map((entry) => {
let location = readOnly<Location>({
pathname: '/',
search: '',
hash: '',
state: null,
key: createKey(), // 通过 random 生成唯一值
...(typeof entry === 'string' ? parsePath(entry) : entry)
}); // 这里的 location 属于是直接创建, HashHistory 中是使用的 window.location
// readOnly方法 可以看做 (obj)=>obj, 并没有太大作用
return location;
});
function push(to: To, state?: any) {
let nextAction = Action.Push;
let nextLocation = getNextLocation(to, state);
function retry() {
push(to, state);
}
// 忽略其他类似的代码
if (allowTx(nextAction, nextLocation, retry)) {
index += 1;
// 别处是调用原生 API, history.pushState
entries.splice(index, entries.length, nextLocation);
applyTx(nextAction, nextLocation);
}
}
// 与 push 类似, 忽略 replace
function go(delta: number) {
// 与HashHistory不同, 也是走的类似 push
let nextIndex = clamp(index + delta, 0, entries.length - 1);
let nextAction = Action.Pop;
let nextLocation = entries[nextIndex];
function retry() {
go(delta);
}
if (allowTx(nextAction, nextLocation, retry)) {
index = nextIndex;
applyTx(nextAction, nextLocation);
}
}
let history: MemoryHistory = {
// 基本相同
};
return history;
}
Navigate
用来改变 当然 location 的方法, 是一个 react-router 抛出的 API
使用方式:
function App() {
// 一旦 user 是有值的, 就跳转至 `/dashboard` 页面了
// 算是跳转路由的一种方案
return <div>
{user && (
<Navigate to="/dashboard" replace={true} />
)}
<form onSubmit={event => this.handleSubmit(event)}>
<input type="text" name="username" />
<input type="password" name="password" />
</form>
</div>
}
源码
export function Navigate({ to, replace, state }: NavigateProps): null {
// 直接调用 useNavigate 来获取 navigate 方法, 并且 useEffect 每次都会触发
// useNavigate 源码在下方会讲到
let navigate = useNavigate();
React.useEffect(() => {
navigate(to, { replace, state });
});
return null;
}
Outlet
用来渲染子路由的元素, 简单来说就是一个路由的占位符
代码很简单, 使用的逻辑是这样
使用方式:
function App(props) {
return (
<HashRouter>
<Routes>
<Route path={'/'} element={<Dashboard></Dashboard>}>
<Route path="qqwe" element={<About/>}/>
<Route path="about" element={<About/>}/>
<Route path="users" element={<Users/>}/>
</Route>
</Routes>
</HashRouter>
);
}
// 其中外层的Dashboard:
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Outlet />
// 这里就会渲染他的子路由了
// 和以前 children 差不多
</div>
);
}
源码
export function Outlet(props: OutletProps): React.ReactElement | null {
return useOutlet(props.context);
}
export function useOutlet(context?: unknown): React.ReactElement | null {
let outlet = React.useContext(RouteContext).outlet;
if (outlet) {
return (
<OutletContext.Provider value={context}>{outlet}</OutletContext.Provider>
);
}
return outlet;
}
useParams
从当前URL所匹配的路径中, 返回一个对象的键/值对的动态参数。
function useParams<
ParamsOrKey extends string | Record<string, string | undefined> = string
>(): Readonly<
[ParamsOrKey] extends [string] ? Params<ParamsOrKey> : Partial<ParamsOrKey>
> {
// 直接获取了 RouteContext 中 matches 数组的最后一个对象, 如果没有就是空对象
let { matches } = React.useContext(RouteContext);
let routeMatch = matches[matches.length - 1];
return routeMatch ? (routeMatch.params as any) : {};
}
useResolvedPath
将给定的`to'值的路径名与当前位置进行比较
在 <NavLink>
这个组件中使用到
function useResolvedPath(to: To): Path {
let { matches } = React.useContext(RouteContext);
let { pathname: locationPathname } = useLocation();
// 合并成一个 json 字符, 至于为什么又要解析, 是为了添加字符层的缓存, 如果是一个对象, 就不好浅比较了
let routePathnamesJson = JSON.stringify(
matches.map(match => match.pathnameBase)
);
// resolveTo 的具体作用在下方讨论
return React.useMemo(
() => resolveTo(to, JSON.parse(routePathnamesJson), locationPathname),
[to, routePathnamesJson, locationPathname]
);
}
useRoutes
useRoutes钩子的功能等同于
相当于是一种 schema 版本, 更好的配置性
使用方式:
如果使用过 umi, 是不是会感觉到一模一样
function App() {
let element = useRoutes([
{ path: "/", element: <Home /> },
{ path: "dashboard", element: <Dashboard /> },
{
path: "invoices",
element: <Invoices />,
children: [
{ path: ":id", element: <Invoice /> },
{ path: "sent", element: <SentInvoices /> }
]
},
{ path: "*", element: <NotFound /> }
]);
return element;
}
源码
// 具体的 routes 对象是如何生成的, 下面的 Routes-createRoutesFromChildren 会讲到
export function useRoutes(
routes: RouteObject[],
locationArg?: Partial<Location> | string
): React.ReactElement | null {
let { matches: parentMatches } = React.useContext(RouteContext);
let routeMatch = parentMatches[parentMatches.length - 1];
// 获取匹配的 route
let parentParams = routeMatch ? routeMatch.params : {};
let parentPathname = routeMatch ? routeMatch.pathname : "/";
let parentPathnameBase = routeMatch ? routeMatch.pathnameBase : "/";
let parentRoute = routeMatch && routeMatch.route;
// 这里上面都是一些参数, 没有就是默认值
// 等于 React.useContext(LocationContext).location, 约等于原生的 location
let locationFromContext = useLocation();
let location;
if (locationArg) { // 对于配置项参数的一些判断
let parsedLocationArg =
typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
location = parsedLocationArg;
} else {
location = locationFromContext;
}
// 如果参数里有则使用参数里的, 如果没有使用 context 的
let pathname = location.pathname || "/";
let remainingPathname =
parentPathnameBase === "/"
? pathname
: pathname.slice(parentPathnameBase.length) || "/";
// matchRoutes 大概的作用是通过pathname遍历寻找,匹配到的路由 具体源码放在下面讲
let matches = matchRoutes(routes, { pathname: remainingPathname });
// 最后调用渲染函数 首先对数据进行 map
// joinPaths 的作用约等于 paths.join("/") 并且去除多余的斜杠
return _renderMatches(
matches &&
matches.map(match =>
Object.assign({}, match, {
params: Object.assign({}, parentParams, match.params),
pathname: joinPaths([parentPathnameBase, match.pathname]),
pathnameBase:
match.pathnameBase === "/"
? parentPathnameBase
: joinPaths([parentPathnameBase, match.pathnameBase])
})
),
parentMatches
);
}
useRoutes-matchRoutes
function matchRoutes(
routes: RouteObject[],
locationArg: Partial<Location> | string,
basename = "/"
): RouteMatch[] | null {
let location =
typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
// 获取排除 basename 的 pathname
let pathname = stripBasename(location.pathname || "/", basename);
if (pathname == null) {
return null;
}
// flattenRoutes 函数的主要作用, 压平 routes, 方便遍历
// 源码见下方
let branches = flattenRoutes(routes);
// 对路由进行排序
// rankRouteBranches 源码见下方
rankRouteBranches(branches);
// 筛选出匹配到的路由 matchRouteBranch源码在下面讲
let matches = null;
for (let i = 0; matches == null && i < branches.length; ++i) {
matches = matchRouteBranch(branches[i], pathname);
}
return matches;
}
useRoutes-matchRoutes-stripBasename
拆分 basename, 代码很简单, 这里就直接贴出来了
function stripBasename(pathname: string, basename: string): string | null {
if (basename === "/") return pathname;
if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
return null;
}
let nextChar = pathname.charAt(basename.length);
if (nextChar && nextChar !== "/") {
return null;
}
return pathname.slice(basename.length) || "/";
}
useRoutes-matchRoutes-flattenRoutes
递归处理 routes, 压平 routes
function flattenRoutes(
routes: RouteObject[],
branches: RouteBranch[] = [],
parentsMeta: RouteMeta[] = [],
parentPath = ""
): RouteBranch[] {
routes.forEach((route, index) => {
let meta: RouteMeta = {
relativePath: route.path || "",
caseSensitive: route.caseSensitive === true,
childrenIndex: index,
route
};
if (meta.relativePath.startsWith("/")) {
meta.relativePath = meta.relativePath.slice(parentPath.length);
}
// joinPaths 源码: (paths)=>paths.join("/").replace(/\/\/+/g, "/")
// 把数组转成字符串, 并且清除重复斜杠
let path = joinPaths([parentPath, meta.relativePath]);
let routesMeta = parentsMeta.concat(meta);
// 如果有子路由则递归
if (route.children && route.children.length > 0) {
flattenRoutes(route.children, branches, routesMeta, path);
}
// 匹配不到就 return
if (route.path == null && !route.index) {
return;
}
// 压平后组件添加的对象
branches.push({ path, score: computeScore(path, route.index), routesMeta });
});
return branches;
}
useRoutes-matchRoutes-rankRouteBranches
对路由进行排序, 这里可以略过,不管排序算法如何, 只需要知道, 知道输入的值是经过一系列排序的就行
function rankRouteBranches(branches: RouteBranch[]): void {
branches.sort((a, b) =>
a.score !== b.score
? b.score - a.score // Higher score first
: compareIndexes(
a.routesMeta.map(meta => meta.childrenIndex),
b.routesMeta.map(meta => meta.childrenIndex)
)
);
}
useRoutes-matchRoutes-matchRouteBranch
匹配函数, 接受参数 branch 就是某一个 rankRouteBranches
function matchRouteBranch<ParamKey extends string = string>(
branch: RouteBranch,
pathname: string
): RouteMatch<ParamKey>[] | null {
let { routesMeta } = branch;
let matchedParams = {};
let matchedPathname = "/";
let matches: RouteMatch[] = [];
// routesMeta 详细来源可以查看 上面的flattenRoutes
for (let i = 0; i < routesMeta.length; ++i) {
let meta = routesMeta[i];
let end = i === routesMeta.length - 1;
let remainingPathname =
matchedPathname === "/"
? pathname
: pathname.slice(matchedPathname.length) || "/";
// 比较, matchPath 源码在下方
let match = matchPath(
{ path: meta.relativePath, caseSensitive: meta.caseSensitive, end },
remainingPathname
);
// 如果返回是空 则直接返回
if (!match) return null;
// 更换对象源
Object.assign(matchedParams, match.params);
let route = meta.route;
// push 到最终结果上, joinPaths 不再赘述
matches.push({
params: matchedParams,
pathname: joinPaths([matchedPathname, match.pathname]),
pathnameBase: joinPaths([matchedPathname, match.pathnameBase]),
route
});
if (match.pathnameBase !== "/") {
matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
}
}
return matches;
}
useRoutes-matchRoutes-matchRouteBranch-matchPath
对一个URL路径名进行模式匹配,并返回有关匹配的信息。
他也是一个保留在外的可用 API
export function matchPath<
ParamKey extends ParamParseKey<Path>,
Path extends string
>(
pattern: PathPattern<Path> | Path,
pathname: string
): PathMatch<ParamKey> | null {
// pattern 的重新赋值
if (typeof pattern === "string") {
pattern = { path: pattern, caseSensitive: false, end: true };
}
// 通过正则匹配返回匹配到的正则表达式 matcher 为 RegExp
let [matcher, paramNames] = compilePath(
pattern.path,
pattern.caseSensitive,
pattern.end
);
// 正则对象的 match 方法
let match = pathname.match(matcher);
if (!match) return null;
// 取 match 到的值
let matchedPathname = match[0];
let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
let captureGroups = match.slice(1);
// params 转成对象 { param:value, ... }
let params: Params = paramNames.reduce<Mutable<Params>>(
(memo, paramName, index) => {
// 如果是*号 转换
if (paramName === "*") {
let splatValue = captureGroups[index] || "";
pathnameBase = matchedPathname
.slice(0, matchedPathname.length - splatValue.length)
.replace(/(.)\/+$/, "$1");
}
// safelyDecodeURIComponent 等于 decodeURIComponent + try_catch
memo[paramName] = safelyDecodeURIComponent(
captureGroups[index] || "",
paramName
);
return memo;
},
{}
);
return {
params,
pathname: matchedPathname,
pathnameBase,
pattern
};
}
useRoutes-matchRoutes-matchRouteBranch-matchPath-compilePath
function compilePath(
path: string,
caseSensitive = false,
end = true
): [RegExp, string[]] {
let paramNames: string[] = [];
// 正则匹配替换
let regexpSource =
"^" +
path
// 忽略尾随的 / 和 /*
.replace(/\/*\*?$/, "")
// 确保以 / 开头
.replace(/^\/*/, "/")
// 转义特殊字符
.replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
.replace(/:(\w+)/g, (_: string, paramName: string) => {
paramNames.push(paramName);
return "([^\\/]+)";
});
// 对于*号的特别判断
if (path.endsWith("*")) {
paramNames.push("*");
regexpSource +=
path === "*" || path === "/*"
? "(.*)$" // Already matched the initial /, just match the rest
: "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
} else {
regexpSource += end
? "\\/*$" // 匹配到末尾时,忽略尾部斜杠
:
"(?:\\b|\\/|$)";
}
let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
// 返回匹配结果
return [matcher, paramNames];
}
useRoutes-_renderMatches
渲染匹配到的路由
function _renderMatches(
matches: RouteMatch[] | null,
parentMatches: RouteMatch[] = []
): React.ReactElement | null {
if (matches == null) return null;
// 通过 context 传递数据
return matches.reduceRight((outlet, match, index) => {
return (
<RouteContext.Provider
children={
match.route.element !== undefined ? match.route.element : <Outlet />
}
value={{
outlet,
matches: parentMatches.concat(matches.slice(0, index + 1))
}}
/>
);
}, null as React.ReactElement | null);
}
Router
为应用程序的其他部分提供context信息
通常不会使用此组件, 他是 MemoryRouter 最终渲染的组件
在 react-router-dom 库中, 也是 BrowserRouter 和 HashRouter 的最终渲染组件
export function Router({
basename: basenameProp = "/",
children = null,
location: locationProp,
navigationType = NavigationType.Pop,
navigator,
static: staticProp = false
}: RouterProps): React.ReactElement | null {
// 格式化 baseName
let basename = normalizePathname(basenameProp);
// memo context value
let navigationContext = React.useMemo(
() => ({ basename, navigator, static: staticProp }),
[basename, navigator, staticProp]
);
// 如果是字符串则解析 根据 #, ? 特殊符号解析 url
if (typeof locationProp === "string") {
locationProp = parsePath(locationProp);
}
let {
pathname = "/",
search = "",
hash = "",
state = null,
key = "default"
} = locationProp;
// 同样的缓存
let location = React.useMemo(() => {
// 这还方法在 useRoutes-matchRoutes-stripBasename 讲过这里就不多说
let trailingPathname = stripBasename(pathname, basename);
if (trailingPathname == null) {
return null;
}
return {
pathname: trailingPathname,
search,
hash,
state,
key
};
}, [basename, pathname, search, hash, state, key]);
// 空值判断
if (location == null) {
return null;
}
// 提供 context 的 provider, 传递 children
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider
children={children}
value={{ location, navigationType }}
/>
</NavigationContext.Provider>
);
}
parsePath
此源码来自于 history 仓库
function parsePath(path: string): Partial<Path> {
let parsedPath: Partial<Path> = {};
// 首先确定 path
if (path) {
// 是否有#号 , 如果有则截取
let hashIndex = path.indexOf('#');
if (hashIndex >= 0) {
parsedPath.hash = path.substr(hashIndex);
path = path.substr(0, hashIndex);
}
// 再判断 ? , 有也截取
let searchIndex = path.indexOf('?');
if (searchIndex >= 0) {
parsedPath.search = path.substr(searchIndex);
path = path.substr(0, searchIndex);
}
// 最后就是 path
if (path) {
parsedPath.pathname = path;
}
}
// 返回结果
return parsedPath;
}
Routes
用来包裹 route 的元素, 主要是通过 useRoutes 的逻辑
function Routes({
children,
location
}: RoutesProps): React.ReactElement | null {
return useRoutes(createRoutesFromChildren(children), location);
}
Routes-createRoutesFromChildren
接收到的参数一般都是 Route children, 可能是多层嵌套的, 最后得的我们定义的 route 组件结构,
它将被传递给 useRoutes 函数
function createRoutesFromChildren(
children: React.ReactNode
): RouteObject[] {
let routes: RouteObject[] = [];
// 使用官方函数循环
React.Children.forEach(children, element => {
if (element.type === React.Fragment) {
// 如果是 React.Fragment 组件 则直接push 递归函数
routes.push.apply(
routes,
createRoutesFromChildren(element.props.children)
);
return;
}
let route: RouteObject = {
caseSensitive: element.props.caseSensitive,
element: element.props.element,
index: element.props.index,
path: element.props.path
}; // route 对象具有的属性
// 同样地递归
if (element.props.children) {
route.children = createRoutesFromChildren(element.props.children);
}
routes.push(route);
});
return routes;
}
useHref
返回完整的链接
export function useHref(to: To): string {
let { basename, navigator } = React.useContext(NavigationContext);
// useResolvedPath 在上面讲过
let { hash, pathname, search } = useResolvedPath(to);
let joinedPathname = pathname;
if (basename !== "/") {
let toPathname = getToPathname(to);
let endsWithSlash = toPathname != null && toPathname.endsWith("/");
joinedPathname =
pathname === "/"
? basename + (endsWithSlash ? "/" : "")
: joinPaths([basename, pathname]);
}
// 可以看做, 路由的拼接, 包括 ? , #
return navigator.createHref({ pathname: joinedPathname, search, hash });
}
resolveTo
解析toArg, 返回对象
function resolveTo(
toArg: To,
routePathnames: string[],
locationPathname: string
): Path {
// parsePath上面已经分析过了
let to = typeof toArg === "string" ? parsePath(toArg) : toArg;
let toPathname = toArg === "" || to.pathname === "" ? "/" : to.pathname;
let from: string;
if (toPathname == null) {
from = locationPathname;
} else {
let routePathnameIndex = routePathnames.length - 1;
// 如果以 .. 开始的路径
if (toPathname.startsWith("..")) {
let toSegments = toPathname.split("/");
// 去除 ..
while (toSegments[0] === "..") {
toSegments.shift();
routePathnameIndex -= 1;
}
to.pathname = toSegments.join("/");
}
// from 复制
from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
}
// 解析, 返回对象
let path = resolvePath(to, from);
if (
toPathname &&
toPathname !== "/" &&
toPathname.endsWith("/") &&
!path.pathname.endsWith("/")
) {
path.pathname += "/";
}
// 确保加上末尾 /
return path;
}
resolveTo-resolvePath
返回一个相对于给定路径名的解析路径对象, 这里的函数也基本都讲过
function resolvePath(to: To, fromPathname = "/"): Path {
let {
pathname: toPathname,
search = "",
hash = ""
} = typeof to === "string" ? parsePath(to) : to;
let pathname = toPathname
? toPathname.startsWith("/")
? toPathname
// resolvePathname
: resolvePathname(toPathname, fromPathname)
: fromPathname;
return {
pathname,
search: normalizeSearch(search),
hash: normalizeHash(hash)
};
}
resolveTo-resolvePath-resolvePathname
function resolvePathname(relativePath: string, fromPathname: string): string {
// 去除末尾斜杠, 再以斜杠分割成数组
let segments = fromPathname.replace(/\/+$/, "").split("/");
let relativeSegments = relativePath.split("/");
relativeSegments.forEach(segment => {
if (segment === "..") {
// 移除 ..
if (segments.length > 1) segments.pop();
} else if (segment !== ".") {
segments.push(segment);
}
});
return segments.length > 1 ? segments.join("/") : "/";
}
useLocation useNavigationType
function useLocation(): Location {
// 只是获取 context 中的数据
return React.useContext(LocationContext).location;
}
同上
function useNavigationType(): NavigationType {
return React.useContext(LocationContext).navigationType;
}
useMatch
function useMatch<
ParamKey extends ParamParseKey<Path>,
Path extends string
>(pattern: PathPattern<Path> | Path): PathMatch<ParamKey> | null {
// 获取 location.pathname
let { pathname } = useLocation();
// matchPath 在 useRoutes-matchRoutes-matchRouteBranch-matchPath 中讲到过
// 对一个URL路径名进行模式匹配,并返回有关匹配的信息。
return React.useMemo(
() => matchPath<ParamKey, Path>(pattern, pathname),
[pathname, pattern]
);
}
useNavigate
此 hooks 是用来获取操作路由对象的
function useNavigate(): NavigateFunction {
// 从 context 获取数据
let { basename, navigator } = React.useContext(NavigationContext);
let { matches } = React.useContext(RouteContext);
let { pathname: locationPathname } = useLocation();
// 转成 json, 方便 memo 对比
let routePathnamesJson = JSON.stringify(
matches.map(match => match.pathnameBase)
);
let activeRef = React.useRef(false);
React.useEffect(() => {
activeRef.current = true;
}); // 控制渲染, 需要在渲染完毕一次后操作
// 路由操作函数
let navigate: NavigateFunction = React.useCallback(
(to: To | number, options: NavigateOptions = {}) => {
if (!activeRef.current) return; // 控制渲染
// 如果 go 是数字, 则结果类似于 go 方法
if (typeof to === "number") {
navigator.go(to);
return;
}
// 解析go
let path = resolveTo(
to,
JSON.parse(routePathnamesJson),
locationPathname
);
if (basename !== "/") {
path.pathname = joinPaths([basename, path.pathname]);
}
// 这一块 就是 前一个括号产生函数, 后一个括号传递参数
// 小小地转换下:
// !!options.replace ?
// navigator.replace(
// path,
// options.state
// )
// : navigator.push(
// path,
// options.state
// )
//
(!!options.replace ? navigator.replace : navigator.push)(
path,
options.state
);
},
[basename, navigator, routePathnamesJson, locationPathname]
);
// 最后返回
return navigate;
}
generatePath
返回一个有参数插值的路径。 原理还是通过正则替换
function generatePath(path: string, params: Params = {}): string {
return path
.replace(/:(\w+)/g, (_, key) => {
return params[key]!;
})
.replace(/\/*\*$/, _ =>
params["*"] == null ? "" : params["*"].replace(/^\/*/, "/")
);
}
他的具体使用:
generatePath("/users/:id", { id: 42 }); // "/users/42"
generatePath("/files/:type/*", {
type: "img",
"*": "cat.jpg"
}); // "/files/img/cat.jpg"
这里的代码可以说是覆盖整个 react-router 80%以上, 有些简单的, 用处小的这里也不再过多赘述了