在用React開發時,常用的路由是react-router ,但除此之外,還有兩個路由方案,因為他們具備 react-router 沒有的特性。
1.?@tanstack/router
1.1. 主要特性
-
100% 推斷的 TypeScript 支持
-
類型安全的導航
-
嵌套路由和布局路由
-
內置的路由加載器,帶有 SWR 緩存
-
為客戶端數據緩存設計(如 TanStack Query、SWR 等)
-
自動路由預取
-
異步路由元素和錯誤邊界
-
基于文件的路由生成
-
類型安全的 JSON 優先搜索參數狀態管理 API
-
路徑和搜索參數模式驗證
-
搜索參數導航 API
-
自定義搜索參數解析器/序列化器支持
-
搜索參數中間件
-
路由匹配/加載中間件
1.2. 基礎使用示例
import React, { StrictMode } from 'react'
import ReactDOM from 'react-dom/client'
import {Outlet,RouterProvider,Link,createRouter,createRoute,createRootRoute} from '@tanstack/react-router'import { TanStackRouterDevtools } from '@tanstack/router-devtools'const rootRoute = createRootRoute({component: () => (<><div className="p-2 flex gap-2"><Link to="/" className={[&.active]:font-bold">Home</Link>{' '}<Link to="/about" className={[&.active]:font-bold">About</Link></div><hr /><Outlet /><TanStackRouterDevtools /></>),
})const indexRoute = createRoute({getParentRoute: () => rootRoute,path: '/',component: function Index() {return (<div className="p-2"><h3>Welcome Home!</h3></div>)},
})const aboutRoute = createRoute({getParentRoute: () => rootRoute,path: '/about',component: function About() {return <div className="p-2">Hello from About!</div>},
})const routeTree = rootRoute.addChildren([indexRoute, aboutRoute])
const router = createRouter({ routeTree })declare module '@tanstack/react-router' {interface Register {router: typeof router}
}const rootElement = document.getElementById('app')!
if (!rootElement.innerHTML) {const root = ReactDOM.createRoot(rootElement)root.render(<StrictMode><RouterProvider router={router} /></StrictMode>,)
}
1.3.?原理淺析
@tanstack/router 的實現原理與 react-router 類似,關注的概念也完全相同,可以理解為它站在 react-router 的肩膀上,在某些細節上做了增強。
歷史記錄的實現:https://github.com/TanStack/router/blob/main/packages/history/src/index.ts
Router 入口:https://github.com/TanStack/router/blob/main/packages/react-router/src/link.tsx
2.?wouter
2.1. 主要特性
-
最小依賴,壓縮后僅 2.1 KB,對比 React Router 的 18.7 KB。
-
同時支持 React 和 Preact。
-
沒有頂級的 <Router /> 組件,它是完全可選的。
-
模仿 React Router 的最佳實踐,提供熟悉的 Route、Link、Switch 和 Redirect 組件。
-
擁有基于 hook 的 API,用于更細粒度地控制路由:useLocation、useRoute 和 useRouter。
2.2. 基礎使用示例
import { Link, Route, Switch } from "wouter";const App = () => (<><Link href="/users/1">Profile</Link><Route path="/about">About Us</Route>{/* Routes below are matched exclusively - the first matched route gets rendered*/}<Switch><Route path="/inbox" component={InboxPage} /><Route path="/users/:name">{(params) => <>Hello, {params.name}!</>}</Route>{/* Default route in a switch */}<Route>404: No such page!</Route></Switch></>
);
2.3.?原理淺析
對于不同路由歷史與定位信息的封裝。
-
https://github.com/molefrog/wouter/blob/v3/packages/wouter/src/use-browser-location.js
-
https://github.com/molefrog/wouter/blob/v3/packages/wouter/src/use-hash-location.js
-
https://github.com/molefrog/wouter/blob/v3/packages/wouter/src/memory-location.js
import { parse as parsePattern } from "regexparam";
import {useBrowserLocation,useSearch as useBrowserSearch,
} from "./use-browser-location.js";
import {useRef,useContext,createContext,isValidateElement,cloneElement,createElement as h,Fragment,forwardRef,useIsomorphicLayoutEffect,useEvent,
} from "./react-deps.js";
import { absolutePath, relativePath, unescape, stripQm } from "./paths.js";// 定義默認的 Router 對象
const defaultRouter = {hook: useBrowserLocation,searchHook: useBrowserSearch,parser: parsePattern,base: "",ssrPath: undefined,ssrSearch: undefined,hrefs: (x) => x,
};// 創建一個 Router 上下文,提供給應用中的其他部分使用
const RouterCtx = createContext(defaultRouter);// 獲取最近的父級 router
export const useRouter = () => useContext(RouterCtx);// 創建一個參數上下文,提供給 `useParams()` 使用,以獲取匹配的參數
const ParamsCtx = createContext({});
export const useParams = () => useContext(ParamsCtx);// 內部版本的 useLocation 函數,避免多余的 useRouter 調用
const useLocationFromRouter = (router) => {const [location, navigate] = router.hook(router);return [unescape(relativePath(router.base, location)),useEvent((to, navOpts) => navigate(absolutePath(to, router.base), navOpts)),];
};// 使用 useRouter 獲取當前的 location
export const useLocation = () => useLocationFromRouter(useRouter());// 獲取當前搜索參數并返回
export const useSearch = () => {const router = useRouter();return unescape(stripQm(router.searchHook(router)));
};// 路由匹配函數
export const matchRoute = (parser, route, path, loose) => {const { pattern, keys } =route instanceof RegExp? { keys: false, pattern: route }: parser(route || "*", loose);const result = pattern.exec(path) || [];const [base, ...matches] = result;return base !== undefined? [true,() => {const groups =keys !== false? Object.fromEntries(keys.map((key, i) => [key, matches[i]])): result.groups;let obj = { ...matches };groups && object.assign(obj, groups);return obj;},...(loose ? [$base] : []),]: [false, null];
};// 使用 useRouter 獲取路由,并匹配路徑
export const useRoute = (pattern) =>matchRoute(useRouter().parser, pattern, useLocation()[0]);// Router 組件,用于提供自定義路由上下文
export const Router = ({ children, ...props }) => {const parent_ = useRouter();const parent = props.hook ? defaultRouter : parent_;let value;const [path, search] = props.ssrPath?.split("?") ?? [];if (search) (props.ssrSearch = search), (props.ssrPath = path);props.hrefs = props.hrefs ?? props.hook?.hrefs;let ref = useRef({}),prev = ref.current,next = prev;for (let k in parent) {const option =k === "base"? parent[k] + (props[k] || ""): props[k] || parent[k];if (prev === next && option !== next[k]) {ref.current = next = { ...next };}next[k] = option;if (option !== parent[k]) value = next;}return h(RouterCtx.Provider, { value, children });
};// 渲染 Route 組件,根據 props 提供不同渲染邏輯
const h_route = ({ children, component }, params) => {if (component) return h(component, { params });return typeof children === "function" ? children(params) : children;
};// Route 組件,用于匹配路徑并渲染對應組件
export const Route = ({ path, nest, match, ...renderProps }) => {const router = useRouter();const [location] = useLocationFromRouter(router);const [matches, params, base] =match ?? matchRoute(router.parser, path, location, nest);if (!matches) return null;const children = base? h(Router, { base }, h_route(renderProps, params)): h_route(renderProps, params);return h(ParamsCtx.Provider, { value: params, children });
};