BrowserRouter
先看一些例子
const App = () => {
  return (
    <BrowserRouter>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Layout />}>
            <Route index element={<Home/>} />
            <Route path="/courses" element={<CourseLayout />}>
              <Route index element={<CourseIndex/>} />
              <Route path="/courses/:id" element={<Course/>} />
            </Route>
          </Route>
          {/* <Route path="*" element={<NotMatch/>} /> */}
        </Routes>
      </BrowserRouter>
    </BrowserRouter>
  )
}
源码实现
- 使用 history库 创建BrowserHistory
- 监听当前history变化, 即当 url 中的location 发生变化时, 会重新setState
- 讲这些参数传到<Router />组件中,<Router />组件 其实就是包含两个provider的 context
export function BrowserRouter({
  basename,
  children,
  window,
}: BrowserRouterProps) {
  let historyRef = React.useRef<BrowserHistory>();
  if (historyRef.current == null) {
    historyRef.current = createBrowserHistory({ window });
  }
  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}
    />
  );
}
- 组件简易实现 
传下去的children 就是 Routes下的children 进行遍历
const Router = (props) => {
  const { 
    basename, 
    children, 
    location: locationProps 
    navigator
  } = props;
  // 对当前locationProps 做处理,最后返回, 这里我直接用‘’ 表示
  const location = {
    pathname: '',
    search: '',
    hash: '',
    state: '',
    key: '',
  }
  // navigator 就是 history
  let navigationContext = React.useMemo(
    () => ({ basename, navigator, static: staticProp }),
    [basename, navigator, staticProp]
  );
  return (
    <NavigationContext.Provider value={navigationContext}>
      <LocationContext.Provider
        children={children}
        value={{ location, navigationType }}
      />
    </NavigationContext.Provider>
  )
}- 再来看 
export function Routes({
  children,
  location,
}: RoutesProps): React.ReactElement | null {
  return useRoutes(createRoutesFromChildren(children), location);
}这里说明一下<Route> 就是一个空方法,但在下面遍历的时候会判断当前组件是不是<Route /> 后面也是会直接收集element 属性
createRoutesFromChildren 是对element.props.children 就会进行递归遍历
在遍历过程中会判断当前组件是不是
已上面例子为例: 就会生成以下结果:
[
  {
    caseSensitive: '',
    element: <Layout />,
    index: '',
    path: '/',
    children: [
      { 
        caseSensitive: '',
        element: <Home />,
        index: true,
        path: undefined,
      },
      {
        caseSensitive: '',
        element: <CourseLayout />,
        index: undefined,
        path: "/courses",
        children: [
          // 略
        ]
      }
    ]
  }
]
export function createRoutesFromChildren(
  children: React.ReactNode
): RouteObject[] {
  let routes: RouteObject[] = [];
  React.Children.forEach(children, (element) => {
    if (!React.isValidElement(element)) {
      return;
    }
    if (element.type === React.Fragment) {
      routes.push.apply(
        routes,
        createRoutesFromChildren(element.props.children)
      );
      return;
    }
    // 判断当前children 是不是<Route /> 组件, 如果不是会报错,
    invariant(
      element.type === Route,
      `[${
        typeof element.type === "string" ? element.type : element.type.name
      }] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>`
    );
    let route: RouteObject = {
      caseSensitive: element.props.caseSensitive,
      element: element.props.element,
      index: element.props.index,
      path: element.props.path,
    };
    if (element.props.children) {
      route.children = createRoutesFromChildren(element.props.children);
    }
    routes.push(route);
  });
  return routes;
}
- 在来看 useRoutes 这个hook, 这个hook 生成了一个ReactNode, 是一个 RouterContext.Provider 生成的节点 - 从上面得到的routes列表以及url 上的location可以得到符合当前地址的所有路由,数组顺序为路由的辈分关系
 比如上面的/courses连接, 会得到一下匹配的路由
 - matches = [ { params: {}, pathname: "/", pathnameBase: "/", route: [ { caseSensitive: '', element: <Layout />, index: '', path: '/', children: [ // 略 ] } ] }, { params: {}, pathname: "/courses", pathnameBase: "/courses", route: [ { caseSensitive: '', element: <CourseLayout />, index: undefined, path: "/courses", children: [ // 略 ] } ] }, { params: {}, pathname: "/courses/", pathnameBase: "/courses", route: [ { caseSensitive: '', element: <CourseIndex />, index: true, path: undefined, } ] } ]- 生成节点, 这里的outlet 可以当做children,在官网上可以用 - <Outlet />表示- children- 如果有element, 那么用provider包裹一下,注意:这里是从孙子节点到爷爷节点反序构建节点的
 - export function _renderMatches( matches: RouteMatch[] | null, parentMatches: RouteMatch[] = [] ): React.ReactElement | null { if (matches == null) return null; 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); }- 可以这么理解
 - // 第一次 let first = ( <RouteContext.Provider> children={ <CourseIndex /> } value={{ outlet: null, matches: matches.slice(0, 3) }} > </RouteContext.Provider> ) // 第二次 let second = ( <RouteContext.Provider> children={ <CourseLayout /> } value={{ outlet: first, matches: matches.slice(0, 2) }} > </RouteContext.Provider> ) // 第三次 let third = ( <RouteContext.Provider> children={ <Layout /> } value={{ outlet: second, matches: matches.slice(0, 1) }} > </RouteContext.Provider> )
- 如果有
 
- 从上面得到的
在嵌套的路由中渲染 children
- 文档地址
- 嵌套路由中使用children, 是使用API中的<Outlet />,
- 我们知道上面在匹配matches的时候, 会进行<RouteContext.Provider />封装,同时传递outlet给他的子组件,那么<OutLet />可以消费他的context
- 实现 
const OutletContext = React.createContext<unknown>(null);
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;
}Link 组件实现
- Link 其实是用 <a />标签 做的,
- 当我们传入自定义onClick事件,会在点击时运行,后续会判断一下event.defaultPrevented, 表明当前事件是否被调用了,如果没有则调用内置internalOnClick事件
export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
  function LinkWithRef(
    { onClick, reloadDocument, replace = false, state, target, to, ...rest },
    ref
  ) {
    let href = useHref(to);
    let internalOnClick = useLinkClickHandler(to, { replace, state, target });
    function handleClick(
      event: React.MouseEvent<HTMLAnchorElement, MouseEvent>
    ) {
      if (onClick) onClick(event);
      if (!event.defaultPrevented && !reloadDocument) {
        internalOnClick(event);
      }
    }
    return (
      <a
        {...rest}
        href={href}
        onClick={handleClick}
        ref={ref}
        target={target}
      />
    );
  }
);useLinkClickHandler 的实现
其实就是调用了 history.push 或者是 history.replace 做跳转
export function useLinkClickHandler<E extends Element = HTMLAnchorElement>(
  to: To,
  {
    target,
    replace: replaceProp,
    state,
  }: {
    target?: React.HTMLAttributeAnchorTarget;
    replace?: boolean;
    state?: any;
  } = {}
): (event: React.MouseEvent<E, MouseEvent>) => void {
  let navigate = useNavigate();
  let location = useLocation();
  let path = useResolvedPath(to);
  return React.useCallback(
    (event: React.MouseEvent<E, MouseEvent>) => {
      if (
        // 表示用户点击了鼠标左键
        event.button === 0 &&
        (!target || target === "_self") &&
        !isModifiedEvent(event) 
      ) {
        event.preventDefault();
        // 判断一下当前应该用 history.replace 还是 history.push
        let replace =
          !!replaceProp || createPath(location) === createPath(path);
        navigate(to, { replace, state });
      }
    },
    [location, navigate, path, replaceProp, state, target, to]
  );
}
useNavigation 实现
一开始 <BrowserHistory /> 下的<Router /> 使用 <NavigationContext.Provider /> 下发 navigator,
最后返回 navigate 方法,实际上就是 history.replace 或者是 history.push
export type Navigator = Pick<History, "go" | "push" | "replace" | "createHref">;export function useNavigate(): NavigateFunction {
  // 这里拿到 navigator, 实际就是 history, 就是一开始 createBrowserHistory
  let { basename, navigator } = React.useContext(NavigationContext);
  let { matches } = React.useContext(RouteContext);
  let { pathname: locationPathname } = useLocation();
  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;
      if (typeof to === "number") {
        navigator.go(to);
        return;
      }
      let path = resolveTo(
        to,
        JSON.parse(routePathnamesJson),
        locationPathname
      );
      if (basename !== "/") {
        path.pathname = joinPaths([basename, path.pathname]);
      }
      // 最后这里 进行 history.replace 或者是 history.push
      (!!options.replace ? navigator.replace : navigator.push)(
        path,
        options.state
      );
    },
    [basename, navigator, routePathnamesJson, locationPathname]
  );
  return navigate;
}