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.构建项目,页面结构如下:
- 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 的基本用法咱们就说到这,参考网站: