这次的版本是 6.2.1
应用
相比拟 5.x 版本, <Switch>元素降级为了<Routes>
简略的 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>(不读取或写入地址栏)。在测试和非浏览器环境中很有用,例如 React Native。
他的源码和其余两个 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) ); // TODO resolveTo return React.useMemo( () => resolveTo(to, JSON.parse(routePathnamesJson), locationPathname), [to, routePathnamesJson, locationPathname] );}
useRoutes
useRoutes钩子的性能等同于<Routes>,但它应用JavaScript对象而不是<Route>元素来定义路由。
相当于是一种 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; } // 压平后组件增加的对象, TODO computeScore 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%以上, 有些简略的, 用途小的这里也不再过多赘述了
参考文档:
- https://reactrouter.com/docs/...
- https://github.com/remix-run/...
- https://github.com/remix-run/...