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 暴露出来

/**
 * 一个路由对象的基本构成
 */
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%以上, 有些简单的, 用处小的这里也不再过多赘述了

参考文档:

posted @ 2022-01-31 23:09  Grewer  阅读(504)  评论(5编辑  收藏  举报