每个不同的应用界面最好都有自己的 url 地址。在单页应用中,我们实际上总是在同一页上,即使有时处于不同的界面,路由地址仍然保持不变。
React 有 React Router 库,为管理 React 应用中的路由提供了一个解决方案。
教程:FullStackOpen2022 / Part 7 / React Router
本文以 routed-anecdotes 应用为例。对应练习 7.1-7.3。
注意: 本文中 React Router 版本为 5。版本 6 的 React Router 有一些突破性的改变,详见:React Router | Upgrading from v5。
1. 使用 React Router
安装 React Router:
npm install react-router-dom
启用 React Router 提供的路由的应用:
App.js:
import React, { useState } from 'react'
import {
Switch,
Route,
Link,
} from 'react-router-dom'
//...
const Menu = () => {
return (
<div>
<Link to="/">anecdotes</Link>
<Link to="/create">create new</Link>
<Link to="/about">about</Link>
</div>
)
}
const App = () => {
//...
return (
<div>
//...
<Switch>
<Route path="/anecdotes/:id">
<Anecdote anecdote={anecdote} />
</Route>
<Route path="/create">
<CreateNew addNew={addNew} />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/">
<AnecdoteList anecdotes={anecdotes} />
</Route>
</Switch>
<Footer />
</div>
)
}
export default App
index.js:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { BrowserRouter } from 'react-router-dom'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
)
路由,或者说在浏览器中基于 URL 的组件的有条件渲染,是通过将组件作为 BrowserRouter
组件的子组件,也就是在 <BrowserRouter>
标签中使用。
A
<BrowserRouter>
stores the current location in the browser’s address bar using clean URLs and navigates using the browser’s built-in history stack.
通常情况下,当地址栏中的 URL 发生变化时,浏览器会加载一个新页面。然而,在 HTML5 History API 的帮助下,BrowserRouter
能够在 React 应用中使用浏览器地址栏中的 URL 进行内部“路由”。因此,即使地址栏中的 URL 发生变化,页面的内容也只是使用 Javascript 进行操作,浏览器不会从服务器上加载新的内容。使用后退和前进的动作,以及做书签,仍然像在一个传统的网页上那样合乎逻辑。
在路由器 BrowserRouter 内部,用链接组件 <Link>
修改地址栏:
<Link to="/create">create new</Link>
基于浏览器 URL 渲染的组件由组件 <Route>
定义,并用一个 <Switch>
组件(或者 <Routes>
)来包装要根据网址渲染的组件:
<Switch>
<Route path="/anecdotes/:id">
<Anecdote anecdote={anecdote} />
</Route>
<Route path="/create">
<CreateNew addNew={addNew} />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/">
<AnecdoteList anecdotes={anecdotes} />
</Route>
</Switch>
2. Parameterized Route
可以在 Route
中定义参数化的 URL:
<Route path="/anecdotes/:id">
<Anecdotes anecdotes={anecdotes} />
</Route>
Anecdotes 组件可以通过 React Router 的 useParams
函数访问 URL 参数。
import {
// ...
useParams
} from "react-router-dom"
const Anecdotes = ({ anecdotes }) => {
const id = useParams().id
const anecdote = anecdotes.find(n => n.id === Number(id))
//...
}
另一种方法是使用 React Router 的 useRouteMatch
hook (或 useMatch
)来计算参数。
import {
//...
useRouteMatch,
} from 'react-router-dom'
//...
const App = () => {
//...
const match = useRouteMatch('/anecdotes/:id')
const anecdote = match
? anecdotes.find((anecdote) => anecdote.id === match.params.id)
: null
return (
<div>
//...
<Switch>
<Route path="/anecdotes/:id">
<Anecdote anecdote={anecdote} />
</Route>
//...
</Switch>
<Footer />
</div>
)
}
3. 使用 Navigate 和 useNavigate
进行重定向
import { Navigate } from "react-router-dom";
//...
<Route path="/users" element={user ? <Users /> : <Navigate replace to="/login" />} />
如果一个用户没有登录,Users 组件就不会被渲染。相反,用户会被组件 Navigate 重定向到登录视图。
React Router 的 useNavigate
函数则可以使浏览器的 URL 以编程方式改变。
import {
// ...
useNavigate
} from 'react-router-dom'
const Login = (props) => {
const navigate = useNavigate()
const onSubmit = (event) => {
event.preventDefault()
props.onLogin('username')
navigate('/')
}
return (
<div>
<h2>login</h2>
<form onSubmit={onSubmit}>
<div>
username: <input />
</div>
<div>
password: <input type='password' />
</div>
<button type="submit">login</button>
</form>
</div>
)
}
随着用户的登录,调用 navigate("/")
,使浏览器的 URL 改变为 /
,应用渲染相应的组件Home。