ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

React Router `<BrowserRouter>` 使用指南:基于浏览器 History API 的声明式客户端路由

React Router `<BrowserRouter>` 使用指南:基于浏览器 History API 的声明式客户端路由 React RouterBrowserRouter使用指南基于浏览器 History API 的声明式客户端路由【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router导读BrowserRouter是 React Router 声明式模式Declarative Mode下最常用的路由器组件它以 JSX 组件的形式声明整套路由配置并借助浏览器原生 History API 驱动地址栏 URL 与路由状态的双向同步无需任何服务端参与即可完成完整的前端路由。阅读本文后你将掌握BrowserRouter的组件签名与全部 Propsbasename、children、useTransitions、window的取值语义、在真实应用中的最小可运行示例、它与HashRouter/MemoryRouter等兄弟组件的取舍以及其在源码中的底层运行机制与测试验证方式。什么是BrowserRouter在 React Router 的分类体系中BrowserRouter属于声明式路由器家族MODES: declarative官方将其描述为一个使用浏览器 History API 做客户端路由的声明式Router。它基于浏览器真实的history.pushState/replaceState/popstate机制工作URL 是唯一的事实来源source of truth。当用户在地址栏输入网址、点击前进/后退按钮或通过代码发起导航时路由状态随之变化反过来路由导航也会改写浏览器地址栏中的 URL。BrowserRouter对应的是传统、无需数据加载能力的声明式路由用法通常与Routes、Route等组件直接组合渲染。如果你的应用还需要 loaders、actions、fetchers 等数据加载能力则应改用数据路由器模式中的createBrowserRouterRouterProvider如果基于 Server Components 或有服务端渲染需求则应参考框架模式与ServerRouter服务端使用StaticRouter。组件签名与 Props 全解从源码的 TypeScript 接口lib.tsx#L780-L809可以完整还原其组件签名function BrowserRouter({ basename, children, useTransitions, window, }: BrowserRouterProps) { // 实现省略见下文底层运行机制 }四个 Props 全部为可选参数功能如下。basename应用的基础路径Application basename。当应用并不部署在域名根路径而是部署在某个子路径下例如由网关代理到/app前缀时通过basename告诉路由器其 URL 前缀BrowserRouter basename/app Routes Route path/ element{Home /} / /Routes /BrowserRouter从源码实现看basename并不会传给 history 对象而是透传给内部的Router组件lib.tsx#L855-L864。Router内部components.tsx#L1316-L1326会先对传入的basename做归一化处理——通过basenameProp.replace(/^\/*/, /)将任意数量的前导斜杠折叠为单个/从而保留用户对尾斜杠语义的控制权随后在匹配路径时通过stripBasename(pathname, basename)把 URL 前缀剥离后再与路由表匹配components.tsx#L1341-L1359。需要注意边界行为当浏览器当前 URL 不以所配置的basename开头时Router的 location 上下文为null组件不会渲染任何子内容并在开发模式下输出形如Router basename/app is not able to match the URL /other/... because it does not start with the basename, so the Router wont render anything.的警告。同时内部导航如Link点击生成的目标 URL 会自动拼接上basename前缀保证地址栏展示与路由匹配始终保持一致。默认值为/。children描述路由配置的Route组件集合。与createBrowserRouter传入路由对象数组不同声明式模式下路由树用嵌套的 JSX 表达。children通常是Routes组件也可以是裸的Route元素在个别场景不使用Routes的情况下children也可以只是任意的 UI 内容此时Router仅负责提供 location 相关的上下文供 hooks 使用。BrowserRouter Routes Route path/ element{HomePage /} / Route path/users/:userId element{UserDetail /} / /Routes /BrowserRouteruseTransitions控制路由器的状态更新是否在内部使用React.startTransition包装。它有三种取值语义lib.tsx#L790-L803取值行为undefined默认所有路由器状态更新都会被包裹在React.startTransition中trueLink与Form引发的导航会被包裹在React.startTransition中同时所有路由器状态更新也都会被包裹false路由器不会在任何导航或状态变更上使用React.startTransition该特性的完整背景见仓库文档 React Transitions声明式/数据/框架模式通用。要点如下React Router v7 开始startTransition已成为默认行为源自 v6.13 引入的future.v7_startTransition标志。但默认行为存在两类问题其一部分应用依赖React.useSyncExternalStore它强制同步更新无法从 transition 的异步语义中获益甚至可能意外显示 Suspense fallback其二React 19 引入的 async transitions 与useOptimistic需要路由器把导航期间的子集状态通过useOptimistic暴露给 UI否则startTransition(() navigate(path))这类用法下useNavigation等 hooks 不会如预期工作。因此useTransitions承担双重职责useTransitions{false}退出opt-out让状态更新脱离startTransition解决useSyncExternalStore场景下的问题useTransitions等价于true增强接入opt-in除保持所有内部状态更新被包裹外还会把useNavigate/useSubmit返回的 Promise 交给startTransition使 transition 持续整个导航过程在框架/数据模式下导航期间的子集路由状态会经由useOptimistic暴露给 UI。需要注意useTransitions三态下对应的源码分支可精确对照lib.tsx#L842-L851——当显式为false时直接调用setStateImpl(newState)同步、非 transition否则一律走React.startTransition(() setStateImpl(newState))。由于默认行为在 React 19 的 API 语境下并不完整规划中会把 opt-in 行为作为 React Router v8 的默认值但useTransitions{false}的 opt-out 开关预计会被保留以满足useSyncExternalStore等特殊场景。三方模式下的写法对应关系为HydratedRouter useTransitions /框架/RouterProvider useTransitions /数据/BrowserRouter useTransitions /声明式。windowWindow对象的覆盖项默认使用全局window实例。传入自定义window的典型场景是单元测试或 jsdom 环境用于把 history 实现绑定到测试提供的 window 上。仓库测试即大量采用该用法例如 concurrent-mode-navigations-test.tsx#L147-L176 中通过BrowserRouter window{getWindow(/, false)}显式注入测试 window 后再配合Routes、React.lazy 与 Suspense 断言导航行为。window最终透传给createBrowserHistory({ window, v5Compat: true })决定底层 popstate 监听与pushState操作的宿主。底层运行机制从源码看懂实现BrowserRouter的实现位于 packages/react-router/lib/dom/lib.tsx#L826-L865整体是一个非常精简的history 订阅器 状态适配器export function BrowserRouter({ basename, children, useTransitions, window }: BrowserRouterProps) { let historyRef React.useRefBrowserHistory(null); if (historyRef.current null) { historyRef.current createBrowserHistory({ window, v5Compat: true }); } let history historyRef.current; let [state, setStateImpl] React.useState({ action: history.action, location: history.location, }); let setState React.useCallback( (newState) { if (useTransitions false) { setStateImpl(newState); } else { React.startTransition(() setStateImpl(newState)); } }, [useTransitions], ); React.useLayoutEffect(() history.listen(setState), [history, setState]); return ( Router basename{basename} children{children} location{state.location} navigationType{state.action} navigator{history} useTransitions{useTransitions} / ); }逐行拆解其中的关键设计单例 history 缓存使用useRef保存 history 实例仅在组件首次渲染时调用一次createBrowserHistory({ window, v5Compat: true })。v5Compat: true是为兼容 v5 语义如对监听器回调触发时机而设的标志浏览器历史的相关实现含popstate监听、pushState/replaceState的调用以及 state 中idx索引维护位于 packages/react-router/lib/router/history.ts其中createBrowserHistory在约 L384 处定义最终委托给getUrlBasedHistory。把 history 快照映射为 React 状态useState初始值取自history.action导航类型即POP/PUSH/REPLACE与history.location。订阅 location 变更通过useLayoutEffect调用history.listen(setState)浏览器 history 实现内部监听popstate事件任何前进/后退或地址栏跳转都会触发回调从而驱动setState更新组件状态。透传核心属性给底层RouterRoutercomponents.tsx#L1299-L1377接收location、navigationType、navigator后分别构建并下发NavigationContext内含basename、navigator、static、useTransitions与LocationContext此后应用任意位置都可以通过useLocation、useNavigate、useParams等 hooks 感知与操纵路由。嵌套限制Router内部会对useInRouterContext()做断言若在已有 Router 内部再次渲染会抛出 You cannot render aRouterinside anotherRouter 错误即一个应用中只能存在一个顶级路由器。实战示例从零搭建最小声明式应用下面是一个可直接复制到 React Vite 项目的、自包含的最小示例。将BrowserRouter置于组件树最外层内嵌Routes描述两条路由import { BrowserRouter, Routes, Route, Link, useParams } from react-router; function Home() { return ( div h1首页/h1 Link to/users/42查看用户 42/Link /div ); } function User() { const { userId } useParams(); return h1用户 #{userId}/h1; } export default function App() { return ( BrowserRouter basename/ Routes Route path/ element{Home /} / Route path/users/:userId element{User /} / /Routes /BrowserRouter ); }要点说明basename/是等效默认值通常可以省略显式写出便于阅读时理解其作用部署在子路径时改为BrowserRouter basename/app所有Link to内部目标会被自动补全为/app/...若使用了 React Suspense 懒加载路由组件React.lazy Suspense在默认的useTransitionsundefined或useTransitions{true}下导航状态更新运行在 transition 中可避免部分不必要的 Suspense fallback 闪烁——仓库测试 concurrent-mode-navigations-test.tsx 即对该组合做了完整覆盖。与其它声明式路由器的对比理解BrowserRouter的定位离不开与其同族的其它组件横向比较组件依赖的 URL 载体适用场景BrowserRouter浏览器 History APIURL path常规客户端路由URL 美观、可被服务端记录HashRouterURLhash片段无法配置服务端回退到index.html的静态托管场景参考 HashRouter 文档MemoryRouter内存中的 location不触碰地址栏测试、非浏览器环境参考 MemoryRouter 文档HistoryRouterunstable外部传入的 history 实例高度定制场景官方明确不鼓励使用否则容易引入与 React Router 内部版本不一致的第二份 history 库参考 HistoryRouter 文档StaticRouter由 props 注入的静态 locationSSR 首屏渲染不响应 location 变化参考 StaticRouter 文档上述所有组件在实现结构上高度同构均在lib.tsx中基于对应 history 工厂createBrowserHistory/createHashHistory/createMemoryHistory建立订阅再把状态透传给共享的Router。选型时若需在无服务端配置能力与URL 形态之间权衡请优先考虑目标部署平台的静态资源服务是否会把未知路径统一回退到应用的 HTML 入口——能回退则选BrowserRouter否则用HashRouter。使用注意事项小结仅用于浏览器环境BrowserRouter依赖window、history等浏览器全局对象不适合在服务端渲染阶段使用SSR 时请使用StaticRouter客户端水合请参考框架模式入口文档 entry.client.tsx 与HydratedRouter。只允许一个顶级路由器不要在任何已处于 Router 上下文内的组件树中再次渲染路由器会触发运行时断言错误。basename 与部署路径必须一致当前 URL 不以 basename 开头时 Router 不渲染内容并给出警告需确保部署的静态服务路径与basename设置吻合。transition 行为按需开关若应用大量使用useSyncExternalStore或对 Suspense fallback 时序敏感请参照上文useTransitions的取值语义显式选择undefined/true/false详见 React Transitions 说明。数据能力需求需要 loaders/actions 的应用应使用createBrowserRouter数据路由器通过createBrowserHistory复用同一套浏览器 history 机制并额外提供并行数据加载、RouterProvider渲染模型与 hydration 数据支持。【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表