升级到 React Router6 看这一篇就够了

2,087 阅读4分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

简介

React Router 是当前位置或 URL 的状态容器。它跟踪位置并在其更改时呈现不同的 。看了好多写的升级文章要么不全,要么是原文直译过来的,拗口不易理解,难受的不行,于是决定自己写。

一、前言

  • react Router6(以下简称V6),大量使用react hooks,因此,如果想升到V6,首先要保证react版本升到16.8及以上。

  • 如果升到了V5.1,那可以平滑过渡到v6,那么[V5.1]主要有哪些变化?

    1. 推荐用 <Route children> 替换掉 <Route render> 或 <Route component>原因
    2. 新增了四个hookUseParams、useLocation、useHistory、useRouteMatch,来访问路由器状态,如当前位置和参数
    3. 移除了里面的的<redirect>原因主要是不利于SEO,客户端重定向可以在Route的render里做

二、升级

2.0 重磅!<Prompt> 目前V6[还不支持],升级请慎重!

个人觉得最重要的信息应该放在最前面,好多人按照官网步骤吭哧吭哧升级,到了最后一步,发现<Prompt/>还不支持,那感觉就好像吃了一把苍蝇。

nice.gif

2.1、依赖

 // 先移除掉之前的依赖 再安装
 npm install react-router-dom

2.2、把所有<Switch>元素更改为 <Routes>

  • 改为<Routes>的主要原因:

    • <Routes>里面包裹的<Route><Link>的path支持相对路由,(/开头的则是绝对路由,否则为相对路由)
    • 寻找路径基于最佳path匹配,而不是按顺序遍历,可避免路由不可达的错误
  • V5、V6 的路由嵌套demo 对比

// V5 写法

import {
  BrowserRouter,
  Switch,
  Route,
  Link,
  useRouteMatch,
} from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/">
          <Home />
        </Route>
        <Route path="/users">
          <Users />
        </Route>
      </Switch>
    </BrowserRouter>
  );
}

function Users() {
  return (
    <div>
      <nav>
        <Link to={`${match.url}/me`}>My Profile</Link>
      </nav>

      <Switch>
        <Route path={`${match.path}/me`}>
          <OwnUserProfile />
        </Route>
        <Route path={`${match.path}/:id`}>
          <UserProfile />
        </Route>
      </Switch>
    </div>
  );
}
// V6 写法 
import {
  BrowserRouter,
  Routes,
  Route,
  Link,
} from "react-router-dom";

// 版本1
function App() {
  return (
    <BrowserRouter>
        // switch 改成 Routes
      <Routes>
        <Route path="/" element={<Home />} />
          // 这里的 * 用来匹配子路由
        <Route path="users/*" element={<Users />} />
      </Routes>
    </BrowserRouter>
  );
}
// 子组件
function Users() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>

      <Routes>
        <Route path=":id" element={<UserProfile />} />
        <Route path="me" element={<OwnUserProfile />} />
      </Routes>
    </div>
  );
}

//版本2
import { Outlet} from "react-router-dom";
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="users" element={<Users />}>
          // children写法嵌套子路由
          <Route path="me" element={<OwnUserProfile />} />
          <Route path=":id" element={<UserProfile />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

function Users() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>
        // 注意这个outlet,用于表示当前所有子路由
      <Outlet />
    </div>
  );
}
  • 小结:
    • 通过V5和V6版本1 对比可以看到,在V6中:
      • <Route path> and <Link to> 都是相对路径,不用再写父路由的 url或者path
      • <Route exact> exact 属性 不再需要,有子路由的时候使用 *
    • 通过 V5和V6版本2 对比可以看到:
      • V5的<Route children> 改为了 <Route element>
      • 当以children的写法写嵌套路由时(版本2),父组件里面写个<Outlet/>即可自动匹配子路由的渲染

tips

  • V6的path比较简单。只支持 :*,且*只能放到path的末位,不能放中间
    • good: <Route path={'abc*'}>
    • bad: <Route path={'a*bc'}>
  • 路由匹配的区分大小写开启 <Routes caseSensitive>
  • v6 中的所有路径匹配都会忽略 URL 上的尾部斜杠。
  • <Link>的path支持同级目录‘.’,和上级目录‘..’(可以当成cd 命令)
// 假如当前url是 /app/dashboard 
// 写法如左,渲染如右
<Link to="stats">    => <a href="/app/dashboard/stats">
<Link to="../stats"> => <a href="/app/stats">
<Link to="../../stats">  => <a href="/stats">
  • <Link>接受state的写法
import { Link } from "react-router-dom";
// V5
<Link to={{ pathname: "/home", state: state }} />
// V6
<Link to="/home" state={state} />

2.3 使用useRoutes 而非‘react-router-config’

  • 配置写法在V5就已经支持了,在V6里大同小异
function App() {
  let element = useRoutes([
    { path: "/", element: <Home /> },
    { path: "dashboard", element: <Dashboard /> },
    {
      path: "invoices",
      element: <Invoices />,
      children: [
        { path: ":id", element: <Invoice /> },
        { path: "sent", element: <SentInvoices /> },
      ],
    },
    { path: "*", element: <NotFound /> },
  ]);
  return element;
}

2.4 使用 useNavigate 替换 useHistory

// V5
import { useHistory } from "react-router-dom";

function App() {
  let history = useHistory();
  function handleClick() {
    history.push("/home");
  }
  return (
    <div>
      <button onClick={handleClick}>go home</button>
    </div>
  );
}

// V6 

function App() {
    let navigate = useNavigate();
  function handleClick() {
    navigate("/home");
  }
  ...
}
  • 用法
    1. 使用navigate() 替换 history.push()
    2. navigate(to, { replace: true }) 替换 history.replace()
    3. 需要state,使用 navigate(to, { state })
    4. go、goBack、goForward,都改为navigate(number)的方式
// V5
import { useHistory } from "react-router-dom";
function App() {
  const { go, goBack, goForward } = useHistory();
  return (
    <>
      <button onClick={() => go(-2)}>
        回退两页
      </button>
      <button onClick={goBack}>返回上一页</button>
      <button onClick={goForward}>前进一页</button>
      <button onClick={() => go(2)}>
        前进两页
      </button>
    </>
  );
}

// V6
import { useNavigate } from "react-router-dom";
function App() {
  const navigate = useNavigate();
  return (
    <>
      <button onClick={() => navigate(-2)}>
        回退两页
      </button>
      <button onClick={() => navigate(-1)}>返回上一页</button>
      <button onClick={() => navigate(1)}>
        前进一页
      </button>
      <button onClick={() => navigate(2)}>
        前进两页
      </button>
    </>
  );
}

2.5 <Link >上的 component属性 不再支持

2.6 NavLink 的变化

  • <NavLink exact> 属性名改为了 <NavLink end>
  • 移除了 activeStyle、activeClassName属性

2.7 StaticRouter 移到了一个新包里

// V5
import { StaticRouter } from "react-router-dom";
// V6
import { StaticRouter } from "react-router-dom/server";

2.8 用useMatch 替代 useRouteMatch

用法上也有了些变化,具体使用参考

  • 接受一个pattern参数,可以是字符串或者对象
 declare function useMatch<ParamKey extends string = string>(
   pattern: PathPattern | string
): PathMatch<ParamKey> | null;

interface PathPattern {
  path: string;
  caseSensitive?: boolean;
  end?: boolean;
}
  • 参数为 pathPattern 对象时,参数名有变化
    • useRouteMatch({ strict }) --> useMatch({ end })
    • useRouteMatch({ sensitive }) -->useMatch({ caseSensitive })

2.9 matchPath的使用变化

  • 第一个参数时 pathPattern对象,第二个 pathname
declare function matchPath<
  ParamKey extends string = string
>(
  pattern: PathPattern | string,
  pathname: string
): PathMatch<ParamKey> | null;
  • pathPattern 参数如上,不再支持 exactstrict,换成了 endcaseSensitive;
// V5 
// This is a React Router v5 app
import { matchPath } from "react-router-dom";

const match = matchPath("/users/123", {
  path: "/users/:id",
  exact: true, // Optional, defaults to false
  strict: false, // Optional, defaults to false
});

// V6
import { matchPath } from "react-router-dom";

const match = matchPath(
  {
    path: "/users/:id",
    caseSensitive: false, // Optional. Should be `true` if the static portions of the `path` should be matched in the same case.
    end: true, // Optional. Should be `true` if this pattern should match the entire URL pathname
  },
  "/users/123"
);

完。