Skip to content

React 路由 ​

一、安装 ​

bash
yarn add react-router-dom
yarn add react-router-dom

二、路由器 ​

路由器说明
BrowserRouter使用 HTML5 pushState / replaceState / popState
HashRouter使用 hash 匹配路由
MemoryRouterURL 保存在内存中,不读写地址栏
NativeRouterReact 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 />
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' })} />
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