跳至正文
Wener Notes

Remix Router

约 7 分钟阅读
标签:路由器
    • npm
      -> @remix-run/router
    • 合并到了 @remix-run/router
    • 支持 ReactDOM 和 ReactNative
    • 支持 MemoryRouter
    • 支持 HashRouter 和 BrowserRouter
    • 支持 Full-Stack
    • 支持 Form
    • remix 核心的路由逻辑也有 @remix/router 实现
      • 基于文件路由
      • 生成路由表
  • 因为同时兼容支持 ReactDOM、ReactNative、Remix Run - 因此牺牲了很多单个平台的高级特性
    • 现在(2024年)重心在 remix run - 因此主要在 ssr/Server Component 等数据服务相关部分
  • 不能嵌套 Router
  • children 绝对路径需要包含 parent 前缀
  • NavLink 使用 routerState 会每次 rerender
  • useNavigate 会 rerender
  • 无法获取到 history - 除非自己做上下文
    • useContext(UNSAFE_NavigationContext).navigator as History
      • unsafe 方式获取 history
    • createHashRouter 和 createBrowserRouter 都无法自己传递 history
    • history 只能做一次 listen - 因此拿到了意义也不大
      • v6.4+
  • useSearchParams 会导致任意参数变化都会 rerender - 无法只监听一个参数
  • 没有过多暴露 low-level 接口
    • 导致部分特殊功能不好实现
bash
npm add react-router-dom
依赖包
TypeScript
// for https://github.com/HTTPArchive/wappalyzer
window.__remixContext; // Remix 全局检测
window.__reactRouterVersion; // ReactRouter
  • CWV - Core Web Vitals
  • Path
    • * - 处理 NotFound
    • :handle
      • const { handle } = useParams()

Notes

v6

  • Router - 不能嵌套
    • NavigationContext - { basename, navigator, static }
      • useNavigate
    • LocationContext - { location, navigationType }
      • useLocation
      • useNavigationType
    • stripBasename - 实现 basename 能力
  • Routes
    • createRoutesFromChildren
    • useRoutes - 支持自定义 location - 默认使用上下文 location
      • matchRoutes - 匹配移除 parentPathnameBase
      • renderMatches
        • RouteContext - { outlet, matches }
  • matchRoutes
    • flattenRoutes
      • computeScore
        • segments.length +
        • splatPenalty=-1 - *
        • indexRouteValue=2 - index: true
          • index 路由不能有 children
        • dynamicSegmentValue=3 - :id
        • emptySegmentValue=1 - ""
        • staticSegmentValue=10 - /user
    • rankRouteBranches
    • matchRouteBranch
  • Context
    • DataRouterContext
    • DataRouterStateContext - Router['state']
    • LocationContext
    • NavigationContext
    • RouteContext
    • RouteErrorContext
  • createMemoryRouter
    • -> createRouter+createMemoryHistory
TypeScript
interface DataRouterContextObject extends Omit<NavigationContextObject, 'future'> {
  router: Router;
  staticContext?: StaticHandlerContext;
}

interface NavigationContextObject {
  basename: string;
  navigator: Navigator;
  static: boolean;
  future: {
    v7_relativeSplatPath: boolean;
  };
}

interface RouteContextObject {
  outlet: React.ReactElement | null;
  matches: RouteMatch[];
  isDataRoute: boolean;
}

@remix-run/router

Router

TypeScript
/**
 * A Router instance manages all navigation and data loading/mutations
 */
export interface Router {
  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Return the basename for the router
   */
  get basename(): RouterInit['basename'];

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Return the future config for the router
   */
  get future(): FutureConfig;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Return the current state of the router
   */
  get state(): RouterState;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Return the routes for this router instance
   */
  get routes(): AgnosticDataRouteObject[];

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Return the window associated with the router
   */
  get window(): RouterInit['window'];

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Initialize the router, including adding history listeners and kicking off
   * initial data fetches.  Returns a function to cleanup listeners and abort
   * any in-progress loads
   */
  initialize(): Router;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Subscribe to router.state updates
   *
   * @param fn function to call with the new state
   */
  subscribe(fn: RouterSubscriber): () => void;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Enable scroll restoration behavior in the router
   *
   * @param savedScrollPositions Object that will manage positions, in case
   *                             it's being restored from sessionStorage
   * @param getScrollPosition    Function to get the active Y scroll position
   * @param getKey               Function to get the key to use for restoration
   */
  enableScrollRestoration(
    savedScrollPositions: Record<string, number>,
    getScrollPosition: GetScrollPositionFunction,
    getKey?: GetScrollRestorationKeyFunction,
  ): () => void;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Navigate forward/backward in the history stack
   * @param to Delta to move in the history stack
   */
  navigate(to: number): Promise<void>;

  /**
   * Navigate to the given path
   * @param to Path to navigate to
   * @param opts Navigation options (method, submission, etc.)
   */
  navigate(to: To | null, opts?: RouterNavigateOptions): Promise<void>;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Trigger a fetcher load/submission
   *
   * @param key     Fetcher key
   * @param routeId Route that owns the fetcher
   * @param href    href to fetch
   * @param opts    Fetcher options, (method, submission, etc.)
   */
  fetch(key: string, routeId: string, href: string | null, opts?: RouterFetchOptions): void;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Trigger a revalidation of all current route loaders and fetcher loads
   */
  revalidate(): void;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Utility function to create an href for the given location
   * @param location
   */
  createHref(location: Location | URL): string;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Utility function to URL encode a destination path according to the internal
   * history implementation
   * @param to
   */
  encodeLocation(to: To): Path;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Get/create a fetcher for the given key
   * @param key
   */
  getFetcher<TData = any>(key: string): Fetcher<TData>;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Delete the fetcher for a given key
   * @param key
   */
  deleteFetcher(key: string): void;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Cleanup listeners and abort any in-progress loads
   */
  dispose(): void;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Get a navigation blocker
   * @param key The identifier for the blocker
   * @param fn The blocker function implementation
   */
  getBlocker(key: string, fn: BlockerFunction): Blocker;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Delete a navigation blocker
   * @param key The identifier for the blocker
   */
  deleteBlocker(key: string): void;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * HMR needs to pass in-flight route updates to React Router
   * TODO: Replace this with granular route update APIs (addRoute, updateRoute, deleteRoute)
   */
  _internalSetRoutes(routes: AgnosticRouteObject[]): void;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Internal fetch AbortControllers accessed by unit tests
   */
  _internalFetchControllers: Map<string, AbortController>;

  /**
   * @internal
   * PRIVATE - DO NOT USE
   *
   * Internal pending DeferredData instances accessed by unit tests
   */
  _internalActiveDeferreds: Map<string, DeferredData>;
}

RouterState

TypeScript
/**
 * State maintained internally by the router.  During a navigation, all states
 * reflect the the "old" location unless otherwise noted.
 */
export interface RouterState {
  /**
   * The action of the most recent navigation
   */
  historyAction: HistoryAction;

  /**
   * The current location reflected by the router
   */
  location: Location;

  /**
   * The current set of route matches
   */
  matches: AgnosticDataRouteMatch[];

  /**
   * Tracks whether we've completed our initial data load
   */
  initialized: boolean;

  /**
   * Current scroll position we should start at for a new view
   *  - number -> scroll position to restore to
   *  - false -> do not restore scroll at all (used during submissions)
   *  - null -> don't have a saved position, scroll to hash or top of page
   */
  restoreScrollPosition: number | false | null;

  /**
   * Indicate whether this navigation should skip resetting the scroll position
   * if we are unable to restore the scroll position
   */
  preventScrollReset: boolean;

  /**
   * Tracks the state of the current navigation
   */
  navigation: Navigation;

  /**
   * Tracks any in-progress revalidations
   */
  revalidation: RevalidationState;

  /**
   * Data from the loaders for the current matches
   */
  loaderData: RouteData;

  /**
   * Data from the action for the current matches
   */
  actionData: RouteData | null;

  /**
   * Errors caught from loaders for the current matches
   */
  errors: RouteData | null;

  /**
   * Map of current fetchers
   */
  fetchers: Map<string, Fetcher>;

  /**
   * Map of current blockers
   */
  blockers: Map<string, Blocker>;
}

Version

React Router v7

React Router v6

  • 替代 React Router v3,v4,v5, Reach Router
  • 支持 嵌套路由
    • <Outlet/> 渲染下一个匹配
  • 支持 相对路径
  • 支持 basename - 路由更加通用,方便嵌套
  • 内置 RouterObject - 直接 JS 配置
    • useRoutes
  • 纯 Hook 重写 - 要求 React 16.8+
  • 不再需要 exact - 使用 * 进行任意匹配
  • element 替代 component 和 render
  • 基于 rank 的路由优先级
  • 不再使用 path-to-regexp - 因此路径写法会有区别
  • React Router v6
  • 暂不支持 Prompt
  • 不再暴露 history - 使用上有很多不方便
    • 可使用 unstable_HistoryRouter 自己提供 history
    • 可能存在引入不同版本 history 导致冲突的问题

  • Upgrade to React Router v6
  • Switch -> Routes
  • Redirect -> Navigate
  • Link 移除 component 熟悉
    • 推荐使用默认 <a>
    • 使用 useLinkClickHandler 构建自定义 Link
  • useHistory -> useNavigate,useSearchParams
    • 接口不一样
    • useHistory 返回 history 实例
    • useNavigate 返回封装后辅助方法
    • 更好的支持 React suspense - useTransition
  • <NavLink exact /> -> <NavLink end />
  • useRouteMatch -> useMatch

  • 2024-06-24 v6.24 支持 Laze Route Discovery/Fog of War
    • 路由没找到时调用 unstable_patchRoutesOnMiss({ path, patch }), 在 createBrowserRouter 指定
      • 通过 patch 对 router 进行补丁
    • 也可以考虑在 route 的 handle 增加 自定义逻辑,然后在 onMiss 时发现进行调用
    • unstable_patchRoutesOnMiss
  • 2023-10-31 v6.18 增加 Fetcher
    • useFetcher, useFetchers
    • Form, useSubmit 增加 navigate/fetcherKey 可指向 fetcher
    • future.v7_fetcherPersist 可以避免在 unmount 时清除 fetcher 状态
  • 2023-10-31 v6.15 增加 redirectDocument 在 loader/action 触发 reload
    • v7 可能会默认为 json
  • 2023-06-23 v6.14 Form 提交支持 json/text, 通过 encType 设置
  • 2023-06-06 v6.12 支持 React.startTransition, 增加 future.v7_startTransition
  • 2023-03-10 v6.9 增加 route.lazy, route.Component, route.ErrorBoundary
    • 可 lazy 的内容 loader, action, element/Component, errorElement/ErrorBoundary, shouldRevalidate, handle
    • 不可以 lazy 的内容 path, index, children
    • 在 loading, submitting 会加载 lazy 的内容
  • 2023-01-26 v6.8 允许 <Link to> 为外部 url
  • 2023-01-18 v6.7 增加 unstable_useBlocker/unstable_usePrompt, <Form preventScrollReset>
  • 2022-12-16 v6.5 支持 ? 表示可选 :lang?/about/us?
  • 2022-09-13 v6.4 增加 Remix Data APIs <RouterProvider>

FAQ

Switch vs Route

<Switch> 只渲染一个路由. 而 <Route> 会渲染所有匹配的路由。

A history only accepts one active listener

  • v6 的 History 只能被 router 监听

关联信息

反向链接、本文链接的其他页面和外部资料。

参考资料

GitHub11 条
其他外链6 条
最近更新commit 906e816编辑