Skip to content

dom 版本 Router

BrowserRouter

基于 Router 生成 Browser 类型的 BrowserRouter ,基于 createBrowserHistory 生成浏览器历史栈管理的 history 对象

  1. 创建 browser 类型的 history 实例对象
  2. 监听 history.listen 从而在浏览器路径切换的时候重新生成新的 location 和 action
ts
export function BrowserRouter({
  basename,
  children,
  future,
  window,
}: BrowserRouterProps) {
  // 1. 创建 history 实例对象
  let historyRef = React.useRef<BrowserHistory>();
  if (historyRef.current == null) {
    historyRef.current = createBrowserHistory({ window, v5Compat: true });
  }

  let history = historyRef.current;
  // 2. 生成url改变的时候 核心需要修改的对象 action 和 location
  let [state, setStateImpl] = React.useState({
    action: history.action,
    location: history.location,
  });
  let { v7_startTransition } = future || {};
  //  创建浏览器url切换回调函数 从而去更新当前history的 action 和 location 对象
  //    使用了React18的新的API React.startTransition去回调,从而使得 history 的更新会非紧急,从而不打断核心的渲染流程
  let setState = React.useCallback(
    (newState: { action: NavigationType; location: Location }) => {
      v7_startTransition && startTransitionImpl
        ? startTransitionImpl(() => setStateImpl(newState))
        : setStateImpl(newState);
    },
    [setStateImpl, v7_startTransition]
  );
  // 2. 监听 history.listen 从而在浏览器路径切换的时候重新生成新的  location 和 action
  React.useLayoutEffect(() => history.listen(setState), [history, setState]);

  return (
    <Router
      basename={basename}
      children={children}
      location={state.location}
      navigationType={state.action}
      navigator={history}
      future={future}
    />
  );
}

react-router 的 Router

其实发现对于 Router 组件来说,其跟 history 对象很像,也是提供一个基础的组件给 BrowserRouter 或者 HashRouter 进行个性化定义;其核心是通过 React.Context 的模式给子孙组件提供 navigationContext 和 locationContext 两个上下文对象

  • navigationContext

用来提供子孙组件中定义路由跳转需要的 方法 和 参数,其核心就是 navigator 属性,在 BrowserRouter 或者 HashRouter 也就是 history 对象。那么我们的子孙组件中 如 useNavigate() 就可以通过 let { basename, future, navigator } = React.useContext(NavigationContext);获取原始的路由跳转方法

  • locationContext

这个主要是提供子孙组件中访问当前 核心location对象 如 useLocation

ts
export function Router({
  // 所有 URL 的基础位置,默认为 '/'
  basename: basenameProp = "/",
  children = null,
  // location 实例对象
  location: locationProp,
  // navigation type
  navigationType = NavigationType.Pop,
  // history
  navigator,
  static: staticProp = false,
  future,
}: RouterProps): React.ReactElement | null {
  // Preserve trailing slashes on basename, so we can let the user control
  // the enforcement of trailing slashes throughout the app
  // 1. 生成 navigationContext 需要的数据
  let basename = basenameProp.replace(/^\/*/, "/");
  let navigationContext = React.useMemo(
    () => ({
      // 所有 URL 的基础位置,默认为 '/'
      basename,
      // 路由跳转方法
      navigator,
      static: staticProp,
      future: {
        v7_relativeSplatPath: false,
        ...future,
      },
    }),
    [basename, future, navigator, staticProp]
  );
  // 2.  生成 locationContext 需要的数据
  // 如果location是字符串类型的 就将其转换成 { pathname search hash }对象
  if (typeof locationProp === "string") {
    locationProp = parsePath(locationProp);
  }

  let {
    pathname = "/",
    search = "",
    hash = "",
    state = null,
    key = "default",
  } = locationProp;

  let locationContext = React.useMemo(() => {
    // 如果 pathname 中存在 basename 前缀 那么就移除basename 前缀
    let trailingPathname = stripBasename(pathname, basename);

    if (trailingPathname == null) {
      return null;
    }
    return {
      location: {
        pathname: trailingPathname,
        search,
        hash,
        state,
        key,
      },
      navigationType,
    };
  }, [basename, pathname, search, hash, state, key, navigationType]);

  if (locationContext == null) {
    return null;
  }

  return (
    <NavigationContext.Provider value={navigationContext}>
      <LocationContext.Provider children={children} value={locationContext} />
    </NavigationContext.Provider>
  );
}