react router v6 详解

1,321 阅读3分钟

React Router v6 非常适合 TypeScript 程序员,因为它附带了 type definitions 。

React Router v6 提供许多很棒的特性,比如useRoutes hook、Outlet API,这可以让我们更好的搭建和管理路由。

接下来,咱们通过一个小例子说一下React Router v6 的用法。

1.先构建一个react+ts 项目。

npx create-react-app react-router-v6-test --template typescript

2.在my-app 项目中安装React Router v6 依赖。

npm install react-router@6 react-router-dom@6

只需要安装下面这两个就够了,无需再像v5那样安装react-router-config,因为其功能已经内置在v6里了。

  • package.json
{
  "name": "react-router-v6-test",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.1.1",
    "@testing-library/user-event": "^13.5.0",
    "@types/jest": "^27.4.1",
    "@types/node": "^16.11.27",
    "@types/react": "^18.0.5",
    "@types/react-dom": "^18.0.1",
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "react-scripts": "5.0.1",
    "typescript": "^4.6.3",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

3.在index.tsx 中,必须用路由组件包裹APP。

  • index.tsx
import { BrowserRouter } from "react-router-dom";
import { createRoot } from "react-dom/client";
import App from "./App";

const container = document.getElementById("root") as HTMLElement;
const root = createRoot(container);
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

若不用路由组件包裹APP,就会报错:

Uncaught Error: useRoutes() may be used only in the context of a component.

注:当前的react 版本是18,API 较之前有了较大变化,可点击查看react 18 更新内容

4.构建项目,页面结构如下:

image-20220419220953514

  • src/main/MainLayout.tsx
import React from "react";
import { Outlet } from "react-router-dom";

const MainLayout: React.FC = (): JSX.Element => {
  return (
    <div>
      MainLayout
      <br />
      <Outlet />
    </div>
  );
};

export default MainLayout;

组件可以将与子路由相匹配的元素显示出来。

  • src/main/ MainView.tsx
import React from 'react';

const MainView: React.FC = (): JSX.Element => {
  return (
    <div>
      MainView
    </div>
  );
};

export default MainView;
  • src/error/PageNotFoundView.tsx
import React from 'react';

const PageNotFoundView: React.FC = (): JSX.Element => {
  return (
    <div>
      PageNotFoundView
    </div>
  );
};

export default PageNotFoundView;
  • src/account/AccountLayout.tsx
import React from "react";
import { Outlet } from "react-router-dom";

const AccountLayout: React.FC = (): JSX.Element => {
  return (
    <div>
      AccountLayout
      <br />
      <Outlet />
    </div>
  );
};

export default AccountLayout;
  • AccountDetailView.tsx
import React from 'react';

const AccountDetailView: React.FC = (): JSX.Element => {
  return (
    <div>
      AccountDetailView
    </div>
  );
};

export default AccountDetailView;
  • src/account/AccountAddView.tsx
import React from 'react';

const AccountAddView: React.FC = (): JSX.Element => {
  return (
    <div>
      AccountAddView
    </div>
  );
};

export default AccountAddView;
  • src/account/AccountListView.tsx
import React from 'react';

const AccountDetailView: React.FC = (): JSX.Element => {
  return (
      <div>
      AccountDetailView
    </div>
  );
};

export default AccountDetailView;

5.用React Router v6 提供的useRoutes hook 搭建路由。

  • App.tsx
import React from 'react';
import {Navigate, useRoutes} from 'react-router-dom';

import AccountAddView from './account/AccountAddView';
import AccountDetailView from './account/AccountDetailView';
import AccountLayout from './account/AccountLayout';
import AccountListView from './account/AccountListView';

import MainLayout from './main/MainLayout';
import MainView from './main/MainView';

import PageNotFoundView from './error/PageNotFoundView';

const App: React.FC = (): JSX.Element => {
  const mainRoutes = {
    path: '/',
    element: <MainLayout />,
    children: [
      {path: '*', element: <Navigate to='/404' />},
      {path: '/', element: <MainView />},
      {path: '404', element: <PageNotFoundView />},
      {path: 'account', element: <Navigate to='/account/list' />},
    ]
  };

  const accountRoutes = {
    path: 'account',
    element: <AccountLayout />,
    children: [
      {path: '*', element: <Navigate to='/404' />},
      {path: ':id', element: <AccountDetailView />},
      {path: 'add', element: <AccountAddView />},
      {path: 'list', element: <AccountListView />},
    ]
  };

  const routing = useRoutes([mainRoutes, accountRoutes]);

  return <>{routing}</>;
};

export default App;

在上面的例子中,可以看出React Router 是支持嵌套路由的,这可以让我们在页面中更加灵活的使用路由。

children 里的子路由会通过Outlet 组件显示出来。

在v6 的Route中,要注意以下规则:

  • path 仅支持2 个占位符:

    • :id 动态参数。
      • 通配符,只能写在路径末端。
  • path中 RegExp 正则表达式无效。

  • exact 已被舍弃。

  • element 相当于以前的component,表示要渲染的view 组件。

6.在首页MainLayout 中建立导航栏,用Link 组件实现页面跳转。

  • src/main/MainLayout.tsx
import React from 'react';
import {Link, Outlet} from 'react-router-dom';

const MainLayout: React.FC = (): JSX.Element => {
  return (
    <>
      <nav>
        <ul>
          <li><Link to='/'>Main Page</Link></li>
          <li><Link to='/account/add'>Add Account</Link></li>
          <li><Link to='/account/list'>List Accounts</Link></li>
          <li><Link to='/account/1'>View Account</Link></li>
          <li><Link to='/something-else'>Not Found</Link></li>
        </ul>
      </nav>
      <Outlet />
    </>
  );
};

export default MainLayout;

若是想用js 跳转页面,要把以前的useHistory 换成useNavigate。

import React from "react";
import { Link, Outlet, useNavigate } from "react-router-dom";

const MainLayout: React.FC = (): JSX.Element => {
  let navigate = useNavigate();
  return (
    <>
        <nav>
          ……
        <button
          onClick={() => {
            navigate("/account/add");
          }}
        >
          Add Account
        </button>
      </nav>
      <Outlet />
    </>
  );
};

export default MainLayout;

7.使用useParams hook 可以获取路由传递的参数。

  • src/account/AccountListView.tsx
import React from 'react';
import {useParams} from 'react-router-dom';

const AccountDetailView: React.FC = (): JSX.Element => {
  const params = useParams();
  return <>{`View Account ID "${params.id}"`}</>;
};

export default AccountDetailView;

关于react-router-v6 的基本用法咱们就说到这,参考网站:

Migrating React Router v5 to v6

Upgrade to React Router v6