React-router-dom 6.x新特性以及迁移

1,772 阅读3分钟

image.png

前言

今天新建了一个新项目,使用到了react-router-dom,当我引入Switch的时候发现有个下划线,报错说是react-router-dom没有导出Switch,去了官网查看官网都变了,才发现已经是6.x版本了。

新特性

1.移除Switch和Redirect组件

react-router移除了Switch,但是引入了功能更强大的Routes, Routes相对于Switch的有点如下:

  • 所有的Route和Link位于Routes里面,旨在有更多简洁且可预测的代码出现在Route和Link内。
  • Routes会根据最佳匹配路线进行匹配,而不是按顺序进行匹配。
  • 路由可以嵌套再一个地方,而不是分散在各个组件,在中小程序中一眼就能看到应用的路由配置,这也是我最喜欢的一点。

这是在V5 react-router中的Switch使用

// This is a React Router v5 app
import React, { FC, ReactElement } from 'react'; 
import { 
  BrowserRouter,
  Route,
  Routes,
  Navigate
} from "react-router-dom";
import Home from '../views/Home';
import Detail from '../views/Detail';

const Router: FC = (): ReactElement => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />}></Route>
        <Route path="/detail" element={<Detail />}></Route>
      </Routes>
    </BrowserRouter>
  )
}
2.移除Redirect

react-router删除所有位于Switch内的Redirect元素,在V6版本中使用Navigate代替 我们使用一个简单的示例来了解新的组件怎么使用

//  router.tsx

import React, { FC } from 'react';
import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom";
import Layout from '../views/Layout';
import Login from '../views/Login';
import Home from '../views/Home';
import Detail from '../views/Detail';
import Cart from '../views/Cart';
import NotFound from '../views/NotFound';
import Article from '../views/Article';

const Router: FC = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Layout />}>  //嵌套路由-所嵌套的子Route使用新的API Outlet 在父组件呈现出来,(类似与Vue的router-view)
          <Route path="home" element={<Home />}></Route>
          <Route path="detail" element={<Detail />}></Route>
          <Route path="cart" element={<Cart />}></Route>
          <Route path="article">
            <Route path=":id" element={<Article />}>

            </Route>
          </Route>
          <Route path="/*" element={<Navigate to="/home" replace={true} />} /> //输入根目录默认跳转到/home 用Navigate代替Redirect
          <Route path="*" element={<NotFound />} />  //用来匹配未知的路由配置
        </Route>
        <Route path="login" element={<Login />}></Route>
      </Routes>
    </BrowserRouter>
  )
}

//Layout.tsx
const Layout: FC = (): ReactElement => {
  return (
    <div className="container">
      <div className="nav">
        <Link to="/home">首页</Link>
        <Link to="/cart">购物车</Link>
        <Link to="/detail">详情</Link>
        <Link to="/article/520">去特定文章</Link>
      </div>

      <div className="content">
        <Outlet />
      </div>
    </div>
  )
}

// Home.tsx
import { useNavigate } from "react-router-dom";
const Home: FC = (): ReactElement => {
  //使用useNavigate代替useHistory navigate(to, { state })
  const navigate = useNavigate();
  return (
    <div>
      Home
      <div>
        <button onClick={() => navigate('/article/1314')}>去第1314篇文章</button>
      </div>
    </div>
  )
}

//Detail.tsx
import { useSearchParams } from "react-router-dom";
const Detail: FC = (): ReactElement => {
  // 获取路径/detail?name=danceli 匹配的name值 -> danceli
  const [searchParams] = useSearchParams();
  return (
    <div>detail - {searchParams.get("name")}</div>
  )
}

//Cart.tsx
const Cart: FC = (): ReactElement => {
  return (
    <div className="container">
      cart
    </div>
  )
}


// Login.tsx
const Login: FC = (): ReactElement => {
  return (
    <div>
      login
    </div>
  )
}

//NotFound.tsx
const NotFound: FC = (): ReactElement => {
  return (
    <div>
      404
    </div>
  )
}

// Article.tsx
import React, { FC, ReactElement } from 'react';
import { useParams } from 'react-router-dom';

const Article: FC = (): ReactElement => {
  //获取/article/:id匹配的article/520的id值520
  const params = useParams();
  return (
    <div>
      article文章: {params.id}
    </div>
  )
}


export default Router;

imageonline-co-gifimage.gif

上面代码已经可以对新的API和hooks的有一定的熟悉,包括获取params,query, 以及嵌套路由,404NotFound, 模拟Redirect(Navigate)。

路由守卫

React中没有封装好的路由守卫钩子,需要我们自己进行封装配置。 首先我们需要分析我们的需求:

我们需要对/detail 和 /article 进行鉴权守卫,在点击进入经过守卫路由的时候,如果当前用户没有登录,就要重定向到/login,且进入Login页面的时候把要进入的页面的pathname传递给Login,以便登录完成后再自动跳转到登录前要进入的页面。

// router.jsx
const Router = () => {
  return (
    <BrowserRouter>
      <Routes>
	<Route path="/" element={<Layout />}>
	<Route path="home" element={<Home />}></Route>
	<Route path="article" element={
            <ProviderRouter>
               <Article />
            </ProviderRouter>
        }>
        </Route>
	<Route path="detail" element={
            <ProviderRouter>
              <Detail />
            </ProviderRouter>
        }>
        </Route>

	<Route path="/" element={<Navigate to="/home" replace={true} />}/>
				</Route>
				
        <Route path="/login" element={<Login />}></Route>
      </Routes>
    </BrowserRouter>
  )
}

// Layout.jsx
import { Link, Outlet } from 'react-router-dom';
const Layout = () => {
  return (
    <div>
      <Link to="/home">首页</Link>
      <Link to="/detail">详情</Link>
      <Link to="/article">去特定文章</Link>
      <div>
        <Outlet />
      </div>
    </div>
  )
}


// ProviderRouter.js
import { Navigate, useLocation } from 'react-router-dom';
const ProviderRouter = ({children}) => {
    //拿到当前的页面的pathname
    const location = useLocation();
    //取到当前的用户信息 isLogin
    const user = useSelector(state => state.user);
    const isLogin = user.isLogin
    //如果没有登录,重定向到Login页面,并且把用户要去的页面通过state传过去
    if(!isLogin) {
	return <Navigate to="/login" state={{from: location}} />
    }
    // 如果已经登录,就直接返回Route匹配的组件
    return children
}


// Login.jsx
import { useLocation, useNavigate } from 'react-router-dom';
const Login = () => {
  
  const dispatch = useDispatch(),
        location = useLocation(),    //拿到当前的路由信息,包括守卫中传来的跳转信息state
        navigate = useNavigate(); 
  const { pathname } = location.state.from;
  
  //登录 并且跳转到刚才守卫传来的用户要跳转的页面
  const handleLogin = () => {
    dispatch({ type: "login" });
    navigate(pathname)
  }
  return (
    <div>
      Login
      <button onClick={handleLogin}>登录</button>
    </div>
  )
}

// Article.jsx
const Article = () => {
  return (
    <div>
      Article
    </div>
  )
}

// Detail.jsx
const Detail = () => {
  return (
    <div>
      Detail
    </div>
  )
}


// Home.jsx
const Home = () => {
  return (
    <div>
      Home
    </div>
  )
}

结果如下

imageonline-co-gifimage.gif

其他

React Router v6使用简化的路径格,仅支持2种占位符:动态:id样式参数和*通配符。

最后

参考文章

react-router V6