陌上人如玉
公子世无双

6.1 React Router v6 核心 API

6.1 React Router v6 核心 API

一句话:v6 的核心变化是「Routes 替代 Switch」「element={<Comp />} 替代 component={Comp}」「相对路径默认相对于父路由」「用 Outlet 渲染子路由」。

一、安装与基本结构

npm i react-router-dom
// main.tsx
import { BrowserRouter } from 'react-router-dom';
import { createRoot } from 'react-dom/client';

createRoot(document.getElementById('root')!).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);
// App.tsx
import { Routes, Route } from 'react-router-dom';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="/users/:id" element={<UserDetail />} />
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
}
Router 组件 说明
BrowserRouter 用 HTML5 History API(路径形如 /about),需要服务端 fallback 到 index.html
HashRouter 用 URL hash(路径形如 #/about),不需要服务端配置,但 URL 不美观、SEO 差
MemoryRouter 内存路由(不改变 URL),用于测试与 React Native
StaticRouter 用于 SSR(需要传入 location)
createBrowserRouter v6.4+ 的数据路由(推荐用于新项目)

二、Routes 与 Route

<Routes>
  {/* ① 基本路由 */}
  <Route path="/" element={<Home />} />

  {/* ② 动态参数 */}
  <Route path="/users/:id" element={<UserDetail />} />

  {/* ③ 嵌套路由(子路由在父路由的 element 里用 <Outlet /> 渲染) */}
  <Route path="/dashboard" element={<DashboardLayout />}>
    <Route index element={<Overview />} />              {/* ⭐ index 路由 */}
    <Route path="settings" element={<Settings />} />    {/* ⭐ 相对路径(等价 /dashboard/settings) */}
    <Route path="profile" element={<Profile />} />
  </Route>

  {/* ④ 通配符(404 兜底) */}
  <Route path="*" element={<NotFound />} />

  {/* ⑤ 多段通配 */}
  <Route path="/docs/*" element={<DocsLayout />} />
</Routes>
⚠️ v5 与 v6 的关键差异:

v5:
  <Switch>
    <Route exact path="/" component={Home} />
    <Route path="/users/:id" render={(props) => <User {...props} />} />
  </Switch>

v6:
  <Routes>
    <Route path="/" element={<Home />} />          {/* ⭐ 默认就是精确匹配 */}
    <Route path="/users/:id" element={<UserDetail />} />
  </Routes>

变化点:
① Switch → Routes
② component / render → element(传 JSX 元素)
③ 不再需要 exact(v6 的匹配更严格,且靠「路由树」结构决定)
④ 相对路径:v6 中子路由的 path 是【相对于父路由】的
⑤ props 注入(history / location / match)→ 改用 Hooks(useNavigate / useLocation / useParams)

三、Link 与 NavLink

import { Link, NavLink } from 'react-router-dom';

// ① Link:不会刷新页面的「a 标签」
<Link to="/about">关于</Link>
<Link to={`/users/${id}`}>详情</Link>
<Link to="/about" replace>关于</Link>              {/* replace:不留下历史记录 */}
<Link to="/search" state={{ from: 'home' }}>搜索</Link>   {/* 传状态 */}
<Link to={`/items/${id}`} relative="path">详情</Link>
// ② NavLink:会自动「标记激活状态」
<NavLink
  to="/about"
  className={({ isActive, isPending, isTransitioning }) =>
    isActive ? 'nav-link active' : 'nav-link'
  }
  style={({ isActive }) => ({ fontWeight: isActive ? 'bold' : 'normal' })}
>
  关于
</NavLink>
// ③ NavLink 的 isActive 判定规则
// 默认:当前路径「是该 to 的路径 或 以其为前缀」时激活
<NavLink to="/users">用户</NavLink>
// 匹配 /users 和 /users/123

// ✅ 需要「精确匹配」用 end
<NavLink to="/" end>首页</NavLink>
// 匹配 / 但不匹配 /about
组件 用途 特点
Link 导航 不刷新页面、保留 SPA 体验
NavLink 导航栏链接 自动提供 isActive
<a href> 整页跳转 会刷新(不推荐在 SPA 内使用)

四、Navigate(重定向)

import { Navigate } from 'react-router-dom';

// ① 声明式重定向(渲染时跳转)
function OldPage() {
  return <Navigate to="/new-page" replace />;
}

// ② 条件重定向(未登录 → 登录页)
function RequireAuth({ children }: { children: React.ReactNode }) {
  const { user } = useAuth();
  const location = useLocation();

  if (!user) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  return <>{children}</>;
}

// ③ 在路由里配置重定向
<Routes>
  <Route path="/old" element={<Navigate to="/new" replace />} />
  <Route path="/" element={<Navigate to="/dashboard" replace />} />
</Routes>

五、Outlet(渲染子路由)

import { Outlet } from 'react-router-dom';

// 父路由组件:提供「布局」,用 Outlet 渲染匹配到的子路由
function DashboardLayout() {
  return (
    <div className="dashboard">
      <Sidebar />
      <main>
        <Outlet />            {/* ⭐ 子路由在这里渲染 */}
      </main>
    </div>
  );
}
// 配套的路由配置
<Routes>
  <Route path="/dashboard" element={<DashboardLayout />}>
    <Route index element={<Overview />} />              {/* /dashboard */}
    <Route path="settings" element={<Settings />} />    {/* /dashboard/settings */}
    <Route path="users">
      <Route index element={<UserList />} />            {/* /dashboard/users */}
      <Route path=":id" element={<UserDetail />} />     {/* /dashboard/users/123 */}
    </Route>
  </Route>
</Routes>
// Outlet 还可以传 context(给子路由用 useOutletContext 读取)
function DashboardLayout() {
  const [user] = useState(loadUser);
  return (
    <div>
      <Sidebar />
      <Outlet context={{ user }} />       {/* ⭐ 传上下文 */}
    </div>
  );
}

function Overview() {
  const { user } = useOutletContext<{ user: User }>();
  return <div>{user.name}</div>;
}

六、常用 Hooks

import {
  useParams,
  useNavigate,
  useLocation,
  useSearchParams,
  useOutletContext,
  useMatch,
  useResolvedPath,
} from 'react-router-dom';

// ① useParams:读取动态参数
function UserDetail() {
  const { id } = useParams<{ id: string }>();
  return <div>用户 {id}</div>;
}

// ② useNavigate:编程式导航
function LoginButton() {
  const navigate = useNavigate();

  const handleLogin = async () => {
    await login();
    navigate('/dashboard');                       // 跳转
    // navigate(-1);                              // 后退
    // navigate('/dashboard', { replace: true }); // 替换历史
    // navigate('/dashboard', { state: { from: 'login' } });
    // navigate('settings');                      // ⭐ 相对路径(当前路由下的 settings)
    // navigate('../');                           // 上一级
  };

  return <button onClick={handleLogin}>登录</button>;
}

// ③ useLocation:读取当前位置
function Analytics() {
  const location = useLocation();
  useEffect(() => {
    track(location.pathname + location.search);   // ⭐ 路由变化时上报
  }, [location]);
  return null;
}

// ④ useSearchParams:读写查询参数
function SearchPage() {
  const [searchParams, setSearchParams] = useSearchParams();
  const keyword = searchParams.get('q') ?? '';
  const page = Number(searchParams.get('page') ?? '1');

  const updateKeyword = (q: string) => {
    setSearchParams({ q, page: '1' });            // ⭐ 会更新 URL 并触发重渲染
    // setSearchParams((prev) => { prev.set('q', q); return prev; });
  };

  return <input value={keyword} onChange={(e) => updateKeyword(e.target.value)} />;
}

// ⑤ useMatch:手动匹配某条路径(进阶)
function Component() {
  const match = useMatch('/users/:id');
  console.log(match?.params.id);
  return null;
}
Hook 作用 对应 v5 的 props
useParams 动态参数 props.match.params
useNavigate 编程式导航 props.history.push
useLocation 当前位置信息 props.location
useSearchParams 查询参数 自己解析 location.search
useOutletContext 读取父路由通过 Outlet 传的 context —
useMatch 手动匹配路径 props.match

七、v5 → v6 迁移对照

v5 v6
<Switch> <Routes>
<Route exact path component={X} /> <Route path element={<X />} />
<Route render={...} /> / children element={<Comp />}
<Redirect to> <Navigate to replace />
{ match, location, history } props useParams / useLocation / useNavigate
history.push() navigate()
useHistory() useNavigate()
withRouter() 直接用 Hooks
子路由必须写完整路径 相对路径(相对于父路由)
<Route path="/" exact> 不需要 exact(匹配更严格)
useRouteMatch() useMatch()

八、常见坑

// ① 用 <a href> 做站内跳转(导致整页刷新)
<a href="/about">关于</a>                  // ❌
<Link to="/about">关于</Link>              // ✅

// ② 嵌套路由忘记在父组件里写 <Outlet />
<Route path="/dashboard" element={<Layout />}>
  <Route path="settings" element={<Settings />} />
</Route>;
function Layout() {
  return <div><Sidebar /></div>;           // ❌ 子路由没有渲染位置
  // ✅ return <div><Sidebar /><Outlet /></div>;
}

// ③ NavLink 的激活判断不符合预期(忘了 end)
<NavLink to="/">首页</NavLink>              // ⚠️ 所有路径都会激活(因为 / 是前缀)
<NavLink to="/" end>首页</NavLink>          // ✅

// ④ BrowserRouter 部署后刷新 404(服务端没配 fallback)
// ✅ Nginx: try_files $uri $uri/ /index.html;
// ✅ 或用 HashRouter

// ⑤ 路由参数变化时组件不重新请求数据
function UserDetail() {
  const { id } = useParams();
  useEffect(() => {
    fetchUser(id);
  }, []);                                  // ❌ 缺 id 依赖 → 切换用户不重新请求
  // ✅ 依赖写 [id]
}

// ⑥ 在同一路径上「参数变化」但组件不重新挂载
<Route path="/users/:id" element={<UserDetail />} />
// 从 /users/1 到 /users/2:React 复用同一个组件实例(因为 element 类型相同)
// → 内部 state 不会重置(如果需要重置,给 element 加 key)
<Route path="/users/:id" element={<UserDetail key={...} />} />   // 需要动态 key

// ⑦ 通配符位置错误(* 应该放在最后)
<Route path="*" element={<NotFound />} />
// ⚠️ 放在前面会吞掉所有路由

// ⑧ 在「非路由组件」里用路由 Hooks(没有 Router 包裹 → 报错)
// ⑨ 多层嵌套时 path 写成了绝对路径(v6 里子路由应为相对路径)
<Route path="/dashboard" element={<Layout />}>
  <Route path="/dashboard/settings" element={<S />} />   // ⚠️ v6 里这样写会拼成 /dashboard/dashboard/settings
  <Route path="settings" element={<S />} />              // ✅
</Route>

// ⑩ 用 useSearchParams 但忘了「它返回的是只读快照 + setter」
const [params] = useSearchParams();
params.set('q', 'x');                       // ❌ 不会更新 URL
setSearchParams({ q: 'x' });                // ✅
// ⑤ 与 ⑥ 的完整修复示例
function UserDetail() {
  const { id } = useParams<{ id: string }>();

  // ✅ 依赖 id → 切换用户时重新请求
  const { data, isLoading } = useQuery({
    queryKey: ['user', id],
    queryFn: () => fetchUser(id!),
  });

  return isLoading ? <Skeleton /> : <div>{data?.name}</div>;
}

// 如果组件内部有「不该跨用户保留」的状态 → 用 key 强制重建
function UserDetailRoute() {
  const { id } = useParams<{ id: string }>();
  return <UserDetailInner key={id} />;
}

九、面试延伸

  1. 「React Router v6 有哪些核心 API?」
    组件:Routes(替代 v5 的 Switch)、Route(用 element 传 JSX)、Link / NavLink(导航,NavLink 提供 isActive)、Navigate(重定向)、Outlet(渲染子路由)。Hooks:useParams(动态参数)、useNavigate(编程式导航)、useLocation(当前位置)、useSearchParams(查询参数)、useOutletContext(读取父路由的 context)。v6.4+ 还有数据路由(createBrowserRouter)与 loader/action。

  2. 「v6 相比 v5 有哪些重要变化?」
    五点:① Switch → Routes;② component / render → element(传 JSX 元素);③ 不再需要 exact(匹配更严格,靠路由树结构决定);④ 子路由的路径是「相对」的(path="settings" 会拼成 /parent/settings);⑤ 不再注入路由 props(history/location/match),改用 Hooks。此外 Redirect 变成 Navigate,withRouter 被移除。

  3. 「Link 和 <a> 有什么区别?」
    <a href> 会触发浏览器整页跳转(重新加载 HTML、重新执行 JS),在 SPA 里这等于「放弃单页应用的优势」(白屏、状态丢失、重复下载资源)。<Link> 是 React Router 的组件——它内部调用 history.pushState 并阻止默认行为,因此在不刷新页面的情况下更新 URL 与渲染视图。站内导航都应该用 Link / NavLink / useNavigate。

  4. 「嵌套路由是怎么工作的?」
    v6 用「路由树 + Outlet」:父路由的 element 是「布局组件」,其中用 <Outlet /> 标记「子路由的渲染位置」。子路由的 path 是相对的(相对于父路由)。匹配 /dashboard/settings 时,React Router 会渲染「DashboardLayout(外层)+ Settings(在 Outlet 处)」。好处是布局可以跨页面复用,且「导航栏保持稳定、只有内容区切换」。此外 index 路由表示「父路由路径本身」的默认子路由。

  5. 「路由参数变化时组件会重新挂载吗?」
    不会。同一路由下参数变化(/users/1 → /users/2)时,React Router 渲染的是同一个 <Route path="/users/:id" element={<UserDetail />} />,React 看到「元素类型相同」就复用组件实例——所以内部的 state、滚动位置等不会重置。要处理这种情况有两种方式:① useEffect 依赖参数(重新请求数据);② 需要「重置内部状态」时,给 element 加 key(key={id})强制重建。

  6. 「useSearchParams 和 URLSearchParams 是什么关系?」
    useSearchParams 返回 [searchParams, setSearchParams],其中 searchParams 是一个 URLSearchParams 实例(只读快照,提供 get/getAll/has/toString 等方法);setSearchParams 用于更新 URL(接受对象、字符串或「修改函数」)。关键是:直接修改 searchParams 不会生效(它是快照),必须调用 setSearchParams 才能更新 URL 并触发重渲染。

  7. 「为什么用 BrowserRouter 部署后刷新会 404?」
    因为 BrowserRouter 使用 HTML5 History API,URL 形如 /dashboard/settings——这是「虚拟路径」,服务器上并没有这个文件。刷新时浏览器直接请求 /dashboard/settings,服务器找不到对应资源就返回 404。解法:① 配置服务端 fallback(把所有未匹配的请求返回 index.html,如 Nginx 的 try_files $uri $uri/ /index.html;);② 或改用 HashRouter(路径在 # 后面,服务器只看到 /,不需要额外配置,但 URL 不美观、SEO 更差)。

  8. 「NavLink 的默认激活规则是什么?什么时候需要 end?」
    默认规则是「当前路径等于 to,或者以 to 为前缀」就激活。所以 <NavLink to="/">首页</NavLink> 会在所有路径下都激活(因为所有路径都以 / 开头),这通常不是期望的。需要「精确匹配」时加 end:<NavLink to="/" end>首页</NavLink>。同理 <NavLink to="/users" /> 会在 /users/123 下也激活(这通常是期望的,因为表示「用户在某个用户的详情页,侧栏的「用户」应该高亮」)。

一句话速记

v6 核心:Routes 替代 Switch、element={<Comp />} 替代 component、不需要 exact、子路由用「相对路径」+ <Outlet /> 渲染、路由信息改用 Hooks(useParams/useNavigate/useLocation/useSearchParams);站内导航用 Link/NavLink(别用 <a>,会整页刷新)、重定向用 Navigate、导航栏高亮用 NavLink(to="/" 要加 end);参数变化不会重新挂载组件(useEffect 依赖参数,或加 key 强制重建);BrowserRouter 需要服务端 fallback(否则刷新 404),或改用 HashRouter。

赞(0) 打赏
未经允许不得转载:陌上寒 » 6.1 React Router v6 核心 API

评论 抢沙发

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

微信扫一扫

支付宝扫一扫