1
This time the version is 6.2.1

use

Compared to version 5.x, the <Switch> element has been upgraded to <Routes>

Simple v6 example:

function App(){
    return  <BrowserRouter>
        <Routes>
            <Route path="/about" element={<About/>}/>
            <Route path="/users" element={<Users/>}/>
            <Route path="/" element={<Home/>}/>
        </Routes>
    </BrowserRouter>
}

context

In react-router, he created two contexts for subsequent use. Of course, these two contexts are internal and no API is exposed.

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

In react-router-dom source parsed we talk BrowserRouter and HashRouter , then this MemoryRouter what is it

He is a <Router> that keeps the history of URLs in memory (does not read or write the address bar). Useful in testing and non-browser environments such as React Native.

The biggest difference between his source code and the other two createMemoryHistory is a 061faa7986ecc3 method, which also comes from the history library

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}
        />
    );
}

So let's take a look at this method now, here only talk about the difference between it 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

The method used to change the location of course is an API thrown by react-router

How to use:


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>
}

source code


export function Navigate({ to, replace, state }: NavigateProps): null {
    // 直接调用 useNavigate 来获取 navigate 方法, 并且  useEffect 每次都会触发
    // useNavigate 源码在下方会讲到
    let navigate = useNavigate();
    React.useEffect(() => {
        navigate(to, { replace, state });
    });

    return null;
}

Outlet

The element used to render the sub-route is simply a placeholder for a route

The code is very simple, the logic used is like this

How to use:


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>
    );
}

source code

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

Returns an object's dynamic parameters of key/value pairs from the path matched by the current 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

Compares the pathname given the `to' value to the current location

<NavLink> the component 061faa7986ef00

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

The useRoutes hook is functionally equivalent to <Routes>, but it uses JavaScript objects instead of <Route> elements to define routes.
Equivalent to a schema version, better configurability

How to use:

If you have used umi, will it feel exactly the same?

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;
}

source code

// 具体的 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

Splitting the basename, the code is very simple, it is directly posted here

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

Process routes recursively, flatten 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

Sorting routes can be skipped here, no matter what the sorting algorithm is, you only need to know that the input value is a series of sorting.

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

Matching function, accepting parameter branch is a 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

Pattern-matches a URL pathname and returns information about the match.
It is also a reserved API available

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

Render the matched route

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

Provide context information for other parts of the application

This component is usually not used, it is the final rendered component of MemoryRouter

In the react-router-dom library, it is also the final rendering component of BrowserRouter and 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

This source code comes from the history repository

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

The element used to wrap the route, mainly through the logic of useRoutes

 function Routes({
                           children,
                           location
                       }: RoutesProps): React.ReactElement | null {
    return useRoutes(createRoutesFromChildren(children), location);
}

Routes-createRoutesFromChildren

The received parameters are generally Route children, which may be nested in multiple layers, and finally the route component structure we defined,
It will be passed to the useRoutes function

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

Back to full link

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

Parse toArg, return object

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

Returns a parsed path object relative to the given path name, and the functions here are basically described

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;
}

Ditto

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

This hook is used to get the operation route object

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

Returns a path with parameter interpolation. The principle is still through regular replacement

function generatePath(path: string, params: Params = {}): string {
    return path
        .replace(/:(\w+)/g, (_, key) => {
            return params[key]!;
        })
        .replace(/\/*\*$/, _ =>
            params["*"] == null ? "" : params["*"].replace(/^\/*/, "/")
        );
}

His specific use:

generatePath("/users/:id", { id: 42 }); // "/users/42"
generatePath("/files/:type/*", {
  type: "img",
  "*": "cat.jpg"
}); // "/files/img/cat.jpg"

The code here can be said to cover more than 80% of the entire react-router, some of which are simple and less useful, and I won't go into details here.

Reference documentation:


Grewer
984 声望28 粉丝

Developer