• reactrouter 源码阅读


    这次的版本是 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 暴露出来

    /**
     * 一个路由对象的基本构成
     */
    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 的源码解析中我们说到了 BrowserRouterHashRouter, 那么这个 MemoryRouter又是什么呢

    他是将 URL 的历史记录保存在内存中的 (不读取或写入地址栏)。在测试和非浏览器环境中很有用,例如 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;
    }
    

    用来改变 当然 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钩子的功能等同于,但它使用JavaScript对象而不是元素来定义路由。
    相当于是一种 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%以上, 有些简单的, 用处小的这里也不再过多赘述了

    参考文档:

  • 相关阅读:
    Crumpet – 使用很简单的响应式前端开发框架
    太赞了!超炫的页面切换动画效果【附源码下载】
    你见过动物是怎么笑的吗?赶紧来看看【组图】
    Treed – 基于拖放 操作的,强大的树形编辑器
    Perfect Scrollbar – 完美的 jQuery 滚动条插件
    Jeet – 先进,直观,灵活的 CSS 网格系统
    舌尖上的设计!10个美味的餐馆和食品网站
    推荐15款最好的 Twitter Bootstrap 开发工具
    Web 前端开发人员和设计师必读精华文章【系列二十五】
    Seen.js – 使用 SVG 或者 Canvas 渲染 3D 场景
  • 原文地址:https://www.cnblogs.com/Grewer/p/15858709.html
Copyright © 2020-2023  润新知