乐趣区

关于javascript:reactrouter-源码阅读

这次的版本是 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 的源码解析中咱们说到了 BrowserRouterHashRouter, 那么这个 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/…
退出移动版