React 路由
一、安装
bash
yarn add react-router-domyarn add react-router-dom二、路由器
| 路由器 | 说明 |
|---|---|
BrowserRouter | 使用 HTML5 pushState / replaceState / popState |
HashRouter | 使用 hash 匹配路由 |
MemoryRouter | URL 保存在内存中,不读写地址栏 |
NativeRouter | React Native 路由 |
StaticRouter | 服务端渲染 |
三、核心组件
Switch / Routes
- v5 使用
Switch,v6 使用Routes - 只呈现匹配到的第一个路由
jsx
// v5
<Switch>
<Route component={Home} path="/home" />
<Route component={NotFound} />
</Switch>
// v6
<Routes>
<Route path="/home" element={<Home />} />
<Route path="*" element={<NotFound />} />
</Routes>// v5
<Switch>
<Route component={Home} path="/home" />
<Route component={NotFound} />
</Switch>
// v6
<Routes>
<Route path="/home" element={<Home />} />
<Route path="*" element={<NotFound />} />
</Routes>Route
jsx
// path — 指定路径
// component — 渲染组件(v5)
// element — 渲染组件(v6)
// render — 内联渲染(v5)
// children — 始终渲染
// v5
<Route path="/user" component={User} />
<Route path="/user" render={routeProps => <User {...routeProps} />} />
// v6
<Route path="/user" element={<User />} />
// sensitive — 区分大小写
// exact — 严格匹配(v5)// path — 指定路径
// component — 渲染组件(v5)
// element — 渲染组件(v6)
// render — 内联渲染(v5)
// children — 始终渲染
// v5
<Route path="/user" component={User} />
<Route path="/user" render={routeProps => <User {...routeProps} />} />
// v6
<Route path="/user" element={<User />} />
// sensitive — 区分大小写
// exact — 严格匹配(v5)优先级(v5):component > render > children
PrivateRoute
jsx
class PrivateRoute extends React.Component {
render() {
const { component: Component, ...rest } = this.props
const token = sessionStorage.getItem('access_token')
return (
<Route
{...rest}
render={props =>
token ? <Component {...props} /> : <Redirect to="/login" />
}
/>
)
}
}class PrivateRoute extends React.Component {
render() {
const { component: Component, ...rest } = this.props
const token = sessionStorage.getItem('access_token')
return (
<Route
{...rest}
render={props =>
token ? <Component {...props} /> : <Redirect to="/login" />
}
/>
)
}
}Redirect / Navigate
jsx
// v5
<Redirect to="/home" />
<Redirect to={{ pathname: '/login', search: '?num=1', state: { from: props.location } }} />
// v6
<Navigate to="/home" replace />// v5
<Redirect to="/home" />
<Redirect to={{ pathname: '/login', search: '?num=1', state: { from: props.location } }} />
// v6
<Navigate to="/home" replace />Link
jsx
<Link to="/home">首页</Link>
<Link to={{ pathname: '/courses', search: '?sort=name', state: { fromDashboard: true } }} />
<Link to={location => ({ ...location, pathname: '/courses' })} /><Link to="/home">首页</Link>
<Link to={{ pathname: '/courses', search: '?sort=name', state: { fromDashboard: true } }} />
<Link to={location => ({ ...location, pathname: '/courses' })} />NavLink(带样式链接)
jsx
<NavLink to="/home" activeClassName="active" activeStyle={{ color: 'red' }} exact /><NavLink to="/home" activeClassName="active" activeStyle={{ color: 'red' }} exact />Outlet(v6 嵌套路由)
jsx
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="profile" element={<Profile />}>
<Route path=":id" element={<MyProfile />} />
<Route path="me" element={<OthersProfile />} />
</Route>
</Routes>
</BrowserRouter>
function Profile() {
return (
<div>
<nav><Link to="me">My Profile</Link></nav>
<Outlet />
</div>
)
}<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="profile" element={<Profile />}>
<Route path=":id" element={<MyProfile />} />
<Route path="me" element={<OthersProfile />} />
</Route>
</Routes>
</BrowserRouter>
function Profile() {
return (
<div>
<nav><Link to="me">My Profile</Link></nav>
<Outlet />
</div>
)
}Prompt(离开提示)
jsx
import { Prompt } from 'react-router-dom'
<Prompt when={isDirty} message="确定离开吗?" />
// v6 使用 useBlocker
function usePrompt(message, when = true) {
const blocker = useCallback((tx) => {
if (window.confirm(message)) tx.retry()
}, [message])
useBlocker(blocker, when)
}import { Prompt } from 'react-router-dom'
<Prompt when={isDirty} message="确定离开吗?" />
// v6 使用 useBlocker
function usePrompt(message, when = true) {
const blocker = useCallback((tx) => {
if (window.confirm(message)) tx.retry()
}, [message])
useBlocker(blocker, when)
}四、路由传参
动态路由
jsx
// 配置
<Route path="/detail/:id" component={Detail} />
// 传值
<NavLink to={'/detail/' + item.id} />
// 取值
props.match.params.id
// 可选参数
<Route path="/detail/:id?" />// 配置
<Route path="/detail/:id" component={Detail} />
// 传值
<NavLink to={'/detail/' + item.id} />
// 取值
props.match.params.id
// 可选参数
<Route path="/detail/:id?" />query
jsx
// 传值
<NavLink to={{ pathname: '/detail', query: { id: 1 } }} />
// 取值
props.location.query.id
// 问号传值
<NavLink to="/home?name=hny&age=18" />
props.location.search // 解析// 传值
<NavLink to={{ pathname: '/detail', query: { id: 1 } }} />
// 取值
props.location.query.id
// 问号传值
<NavLink to="/home?name=hny&age=18" />
props.location.search // 解析state
jsx
// 传值
<NavLink to={{ pathname: '/detail', state: { id: 1 } }} />
// 取值
props.location.state.id// 传值
<NavLink to={{ pathname: '/detail', state: { id: 1 } }} />
// 取值
props.location.state.id编程式导航传参
jsx
<button onClick={() => this.props.history.push('/detail/' + item.id)}><button onClick={() => this.props.history.push('/detail/' + item.id)}>五、编程式导航
v5(useHistory)
jsx
import { useHistory } from 'react-router-dom'
function HomeButton() {
const history = useHistory()
return <button onClick={() => history.push('/home')}>Go Home</button>
}
// 方法
history.push('/home')
history.replace('/home')
history.go(-1)
history.goBack()
history.goForward()import { useHistory } from 'react-router-dom'
function HomeButton() {
const history = useHistory()
return <button onClick={() => history.push('/home')}>Go Home</button>
}
// 方法
history.push('/home')
history.replace('/home')
history.go(-1)
history.goBack()
history.goForward()v6(useNavigate)
jsx
import { useNavigate } from 'react-router-dom'
const navigate = useNavigate()
navigate('/home')
navigate('/home', { replace: true })import { useNavigate } from 'react-router-dom'
const navigate = useNavigate()
navigate('/home')
navigate('/home', { replace: true })withRouter
将路由对象注入非路由直接子组件:
jsx
import { withRouter } from 'react-router-dom'
function App(props) {
console.log(props.history, props.location, props.match)
}
export default withRouter(App)import { withRouter } from 'react-router-dom'
function App(props) {
console.log(props.history, props.location, props.match)
}
export default withRouter(App)六、Hooks API
useParams
jsx
import { useParams } from 'react-router-dom'
const params = useParams()
console.log(params) // { id: '123' }import { useParams } from 'react-router-dom'
const params = useParams()
console.log(params) // { id: '123' }useLocation
jsx
import { useLocation } from 'react-router-dom'
const location = useLocation()
// { pathname, search, hash, state, key }import { useLocation } from 'react-router-dom'
const location = useLocation()
// { pathname, search, hash, state, key }useRouteMatch
jsx
import { useRouteMatch } from 'react-router-dom'
const match = useRouteMatch('/about')
return match && <div>About组件</div>import { useRouteMatch } from 'react-router-dom'
const match = useRouteMatch('/about')
return match && <div>About组件</div>七、路由封装
配置路由
js
const routes = [
{
path: '/',
component: Home,
exact: true,
meta: { title: '首页' }
},
{
path: '/about',
component: About,
children: [
{ path: '/about/my', component: My },
{ redirect: '/about/me' }
]
}
]const routes = [
{
path: '/',
component: Home,
exact: true,
meta: { title: '首页' }
},
{
path: '/about',
component: About,
children: [
{ path: '/about/my', component: My },
{ redirect: '/about/me' }
]
}
]封装 Router 组件
jsx
class App extends React.Component {
renderRoute = (routes) => {
return routes.map(({ children, ...route }) => {
let routeView = null
if (children) {
routeView = <Switch>{this.renderRoute(children)}</Switch>
}
if (route.redirect) {
return <Redirect key={route.redirect} to={route.redirect} />
}
return <WrapRouter {...route} routeView={routeView} key={route.path} />
})
}
render() {
return <Switch>{this.renderRoute(routes)}</Switch>
}
}class App extends React.Component {
renderRoute = (routes) => {
return routes.map(({ children, ...route }) => {
let routeView = null
if (children) {
routeView = <Switch>{this.renderRoute(children)}</Switch>
}
if (route.redirect) {
return <Redirect key={route.redirect} to={route.redirect} />
}
return <WrapRouter {...route} routeView={routeView} key={route.path} />
})
}
render() {
return <Switch>{this.renderRoute(routes)}</Switch>
}
}WrapRouter
jsx
const WrapRouter = ({ component: Component, routeView, ...rest }) => {
useEffect(() => {
if (rest.path === window.location.pathname && rest.meta.title) {
document.title = rest.meta.title
}
}, [rest])
return (
<Route
{...rest}
exact
render={routerProps => <Component {...routerProps} routeView={routeView} />}
/>
)
}const WrapRouter = ({ component: Component, routeView, ...rest }) => {
useEffect(() => {
if (rest.path === window.location.pathname && rest.meta.title) {
document.title = rest.meta.title
}
}, [rest])
return (
<Route
{...rest}
exact
render={routerProps => <Component {...routerProps} routeView={routeView} />}
/>
)
}八、路由嵌套
v5
jsx
function Profile() {
let { path, url } = useRouteMatch()
return (
<div>
<nav>
<Link to={`${url}/me`}>My Profile</Link>
</nav>
<Switch>
<Route path={`${path}/me`}><MyProfile /></Route>
<Route path={`${path}/:id`}><OthersProfile /></Route>
</Switch>
</div>
)
}function Profile() {
let { path, url } = useRouteMatch()
return (
<div>
<nav>
<Link to={`${url}/me`}>My Profile</Link>
</nav>
<Switch>
<Route path={`${path}/me`}><MyProfile /></Route>
<Route path={`${path}/:id`}><OthersProfile /></Route>
</Switch>
</div>
)
}v6
jsx
function Profile() {
return (
<div>
<nav><Link to="me">My Profile</Link></nav>
<Routes>
<Route path="me" element={<MyProfile />} />
<Route path=":id" element={<OthersProfile />} />
</Routes>
</div>
)
}function Profile() {
return (
<div>
<nav><Link to="me">My Profile</Link></nav>
<Routes>
<Route path="me" element={<MyProfile />} />
<Route path=":id" element={<OthersProfile />} />
</Routes>
</div>
)
}九、404 与 historyApiFallback
js
// webpack 配置
historyApiFallback: true// webpack 配置
historyApiFallback: true@reach/router
react-router-dom 作者的同系列作品,更简洁
安装:yarn add @reach/router
jsx
import { Router, Link, navigate } from '@reach/router'
function App() {
return (
<Router>
<Home path="/" />
<Dashboard path="dashboard" />
</Router>
)
}import { Router, Link, navigate } from '@reach/router'
function App() {
return (
<Router>
<Home path="/" />
<Dashboard path="dashboard" />
</Router>
)
}特点:
- 无需 exact,默认精确匹配
- 自动处理路由嵌套
- 内置
navigate()函数 - 通过
default属性匹配 404
Cao Note