React Router v6新特性簡介及遷移

掘金地址:https://juejin.cn/post/7053031843376922660
如需轉(zhuǎn)載,請注明出處

簡介

React Router庫包含三個(gè)不同的npm包,以下每個(gè)包都有不同的用途。

  • react-router :核心庫,包含 React Router 的大部分核心功能,包括路由匹配算法和大部分核心組件和鉤子。
  • react-router-dom:React應(yīng)用中用于路由的軟件包,包括react-router的所有內(nèi)容,并添加了一些特定于 DOM 的 API,包括BrowserRouterHashRouterLink。
  • react-router-native: 用于開發(fā)React Native應(yīng)用,包括react-router的所有內(nèi)容,并添加了一些特定于 React Native 的 API,包括NativeRouterLink。

版本

以下v5從5.1.2所有版本都列出了。v6的還有很多alpha和beta小版本沒有列出。

新項(xiàng)目目前用了6.0.2,之前舊項(xiàng)目用的是5.1.2。

react-router-dom npm地址

版本 下載量 發(fā)布時(shí)間(對比日期2022.1.14)
6.2.1 818,595 1個(gè)月前
6.0.2 464,961 2個(gè)月前
6.0.0 5,540 2個(gè)月前
5.3.0 1,612,985 4個(gè)月前
5.2.1 68,038 5個(gè)月前
6.0.0-beta.0 62,966 2年前
5.2.0 1,734,184 2年前
6.0.0-alpha.0 7 2年前
5.1.2 462,691 2年前

相比v5,打包后的包大小,從20.8k 減少到 10.8k。包分析網(wǎng)站

image.png
image.png

安裝

// npm 安裝
npm install react-router-dom@6

// yarn 安裝
yarn add react-router-dom@6

啟用全局路由模式

全局路由有常用兩種路由模式可選:HashRouter 和 BrowserRouter 分別是hash模式和history模式。

采用BrowserRouter:

import * as React from "react";
import * as ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);

App.jsx 設(shè)置路由

import './App.css';
import { Routes, Route, Link } from "react-router-dom"
function App() {
  return (
    <div className="App">
      <header className="App-header">
        <Routes>
          <Route path="/" element={<Home />}></Route>
          <Route path="/about" element={<About />}></Route>
        </Routes>
      </header>
    </div>
  );
}
function Home() {
  return <div>
    <main>
      <h2>Welcome to the homepage</h2>
    </main>
    <nav>
      <Link to="/about">about</Link>
    </nav>
  </div>
}
function About() {
  return <div>
    <main>
      <h2>Welcome to the about page</h2>
    </main>
    <nav>
      <ol>
        <Link to="/">home</Link>
        <Link to="/about">about</Link>
      </ol>
 
    </nav>
  </div>
}
export default App;

寫在前面

如果您剛剛開始使用 React Router,或者您想在新應(yīng)用中試用 v6,請參閱入門指南。

以下內(nèi)容主要敘述的是與React Router v5版本對比的新特性及如何進(jìn)行快速遷移。

升級前準(zhǔn)備

1. 升級到React v16.8

React Router v6 大量使用React hooks,因此在嘗試升級到 React Router v6 之前,您需要使用 React 16.8 或更高版本。好消息是 React Router v5 與 React >= 15 兼容,因此如果您使用的是 v5(或 v4),您應(yīng)該能夠在不接觸任何路由器代碼的情況下升級 React。

2. 升級到React Router v5.1(非必要)

如果您先升級到 v5.1,則切換到 React Router v6 會更容易。因?yàn)樵?v5.1 中,發(fā)布了對元素處理的增強(qiáng),<Route children>這將有助于平滑過渡到 v6。不要使用<Route component>和<Route render>props,而是在任何地方使用常規(guī)元素<Route children>并使用鉤子來訪問路由器的內(nèi)部狀態(tài)。

升級到 React Router v6

  • <Switch>重命名為<Routes>。
  • <Route>的新特性變更。
  • 嵌套路由變得更簡單。
  • 用useNavigate代替useHistory。
  • 新鉤子useRoutes代替react-router-config。

1. <Switch>重命名為<Routes>

React Router v6 引入了一個(gè)Routes的組件,類似于以前的Switch,但功能更強(qiáng)大。它包括相對路由和鏈接、自動(dòng)路由排名、嵌套路由和布局等功能。

// v5
<Switch>
    <Route exact path="/"><Home /></Route>
    <Route path="/profile"><Profile /></Route>
</Switch>
 
 
// v6
<Routes>
    <Route path="/" element={<Home />} />
    <Route path="profile/*" element={<Profile />} />
</Routes>

2.<Route>的新特性變更

Route負(fù)責(zé)渲染React組件的UI。在v6中會用到兩個(gè)屬性:

  • path:與當(dāng)前頁面對應(yīng)的URL匹配。
  • element:這個(gè)是新增的,用于決定路由匹配時(shí),渲染哪個(gè)組件。在v5的時(shí)候,我們通常會用到component這個(gè)屬性,或者是render。

簡單來說,component/render被element替代

// v5
<Route path=":userId" component={Profile} />
<Route
  path=":userId"
  render={routeProps => (
    <Profile routeProps={routeProps} animate={true} />
  )}
/>

// v6
<Route path=":userId" element={<Profile />} />
<Route path=":userId" element={<Profile animate={true} />} />

3. 嵌套路由更簡單

嵌套路由在實(shí)際應(yīng)用中很常見,如以下兩個(gè)頁面,User這個(gè)組件是共用的,切換路由的時(shí)候,僅改變Profile和Posts子組件。

image.png

在React Router v5中,必須明確定義嵌套路由,React Router v6并非如此。它從React Router庫中挑選了一個(gè)名為 Outlet 的最佳元素,為特定路由呈現(xiàn)任何匹配的子元素。用起來和Vue Router里的<router-view>差不多。

3.1 Outlet實(shí)現(xiàn)嵌套路由

  • v5中實(shí)現(xiàn)嵌套路由
// v5
import {
  BrowserRouter,
  Switch,
  Route,
  Link,
  useRouteMatch
} from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/profile" component={Profile} />
      </Switch>
    </BrowserRouter>
  );
}

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中實(shí)現(xiàn)嵌套路由,可以刪除字符串匹配邏輯。不需要任何useRouteMatch()。利用新API:Outlet( 利用下面第5點(diǎn)說到的useRoutes,可以進(jìn)一步簡化。
import { Outlet } from 'react-router-dom';
function App() {
  return (
    <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>
      {/*將直接根據(jù)上面定義的不同路由參數(shù),渲染<MyProfile />或<OthersProfile /> */}
      <Outlet />
    </div>
  )
}

3.2 多個(gè)<Routes />

以前,我們只能 在React App中使用一個(gè) Routes。但是現(xiàn)在我們可以在React App中使用多個(gè)路由,這將幫助我們基于不同的路由管理多個(gè)應(yīng)用程序邏輯。

import React from 'react';
import { Routes, Route } from 'react-router-dom';

function Dashboard() {
  return (
    <div>
      <p>Look, more routes!</p>
      <Routes>
        <Route path="/" element={<DashboardGraphs />} />
        <Route path="invoices" element={<InvoiceList />} />
      </Routes>
    </div>
  );
}

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="dashboard/*" element={<Dashboard />} />
    </Routes>
  );
}

4. 用useNavigate代替useHistory

從一目了然改到雙目失明。。。

// v5
import { useHistory } from 'react-router-dom';

function MyButton() {
  let history = useHistory();
  function handleClick() {
    history.push('/home');
  };
  return <button onClick={handleClick}>Submit</button>;
};

現(xiàn)在,history.push()將替換為navigation()

// v6
import { useNavigate } from 'react-router-dom';

function MyButton() {
  let navigate = useNavigate();
  function handleClick() {
    navigate('/home');
  };
  return <button onClick={handleClick}>Submit</button>;
};

history的用法也將被替換成:

// v5
history.push('/home');
history.replace('/home');

// v6
navigate('/home');
navigate('/home', {replace: true});

5. 用新鉤子useRoutes代替react-router-config

這個(gè)hooks用完,比上面直接寫router組件的jsx文件要簡潔很多

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 /> }
      ]
    },
    // 404找不到
    { path: '*', element: <NotFound /> }
  ]);
  return element;
}

項(xiàng)目實(shí)踐

以下例子都基于useRoutes

tips:例子中fullPath這個(gè)屬性是自己定義的非官方屬性,因?yàn)榍短茁酚傻臅r(shí)候,path只需要寫相對的路徑。為了方便直接看到包含父路徑的完整路徑,所以定義這個(gè)用于查看,實(shí)際React Router并不會用到這個(gè)屬性。

1. 懶加載

import { useRoutes } from 'react-router-dom'

// 懶加載
const lazyLoad = (path: string) => {
    const Comp = React.lazy(() => import(`@page/${path}`))
    return (
        <React.Suspense fallback={<>加載中...</>}>
            <Comp />
        </React.Suspense>
    )
}

const routers = [
    {
        name: '項(xiàng)目',
        path: '/',
        element: <Backbone />,
        children: [
            {
                name: '首頁',
                path: 'index/*',
                // 這個(gè)屬性實(shí)際路由渲染不會用到。
                fullPath: '/index',
                element: lazyLoad('IndexPage')
            }
        ]
]

const Index = () => useRoutes(routers)

export default Index

2. Index路由:默認(rèn)子路由

用于嵌套路由,僅匹配父路徑時(shí),設(shè)置渲染的組件。 解決當(dāng)嵌套路由有多個(gè)子路由但本身無法確認(rèn)默認(rèn)渲染哪個(gè)子路由的時(shí)候,可以增加index屬性來指定默認(rèn)路由。

index路由和其他路由不同的地方是它沒有path屬性,他和父路由共享同一個(gè)路徑。

例如: 以下路由設(shè)置了兩個(gè)子路由,分別是/foo/invoices 和 /foo/activity 。但是當(dāng)頁面直接訪問 /foo的時(shí)候,頁面就不知道怎么渲染了。這時(shí)候可以通過index設(shè)置默認(rèn)展示到invoices。

const routers = [
    {
        name: '項(xiàng)目',
        path: '/foo',
        element: <Layout />,
        children: [
            // 僅匹配到父級路由時(shí), 設(shè)置index為true,不需要指定path
            { index: true, element: lazyLoad('Invoices') },
            {
                name: '首頁',
                path: 'invoices',
                element: lazyLoad('Invoices')
            },
            {
                    name: 'about',
                path: 'activity',
                element: lazyLoad('Activity')
            }
        ]
   }
]

3. 404

廢棄了V5中的Redirect

  // v5 廢棄了
   const routers = [
        { path: 'home', redirectTo: '/' }
  ]
  
  // 404可以這么寫
  const routers = [
     {
          name: '404',
          path: '*',
          element: <NoMatch />
      }
   ]

4. 動(dòng)態(tài)路由

我們經(jīng)常需要把某種模式匹配到的所有路由,全都映射到同個(gè)組件。例如新聞詳情頁,對應(yīng)不同新聞,都對應(yīng)到News組件,路徑可能為 news/1,news/2...。其中數(shù)字1為新聞的id。這個(gè)是動(dòng)態(tài)變化。

  • 動(dòng)態(tài)路徑參數(shù) 以冒號開頭
const routers = [
        {
        name: '公告詳情頁',
        // 動(dòng)態(tài)路徑參數(shù) 以冒號開頭
        path: 'noticeDetail/:id',
        fullPath: '/noticeDetail',
        element: lazyLoad('NoticeDetail')
    },
    {
        name: '幫助中心詳情頁',
        path: 'helpCenterDetail/:fid/:sid',
        fullPath: '/helpCenterDetail',
        element: lazyLoad('HelpCenterDetail')
    }
]
  • useParams 用于組件獲取動(dòng)態(tài)路徑的參數(shù)

    例如上面這個(gè)例子,有兩個(gè)動(dòng)態(tài)參數(shù)fid和sid,在HelpCenterDetail組件:

import { useParams } from 'react-router-dom'
const { fid, sid } = useParams()

5. 路由通配符

支持以下幾種通配符號。注意:這里的*只能用在/后面,不能用在實(shí)際路徑中間

/groups
/groups/admin
/users/:id
/users/:id/messages
/files/*
/files/:id/*

以下這些v6里面不支持

/users/:id?
/tweets/:id(\d+)
/files/*/cat.jpg
/files-*

6. url搜索參數(shù)

useSearchParams的使用。

例如 :http://URL/user?id=111,獲取id對應(yīng)的值

import { useSearchParams } from 'react-router-dom'

const [searchParams, setSearchParams] = useSearchParams()

// 獲取參數(shù)
searchParams.get('id')

// 判斷參數(shù)是否存在
searchParams.has('id')

// 同時(shí)頁面內(nèi)也可以用set方法來改變路由
setSearchParams({"id":2})

7. withRouter

v6不再提供withRouter,略坑。

官方解釋:這個(gè)問題通常源于您使用的是不支持鉤子的 React 類組件。在 React Router v6 中,我們完全接受了鉤子并使用它們來共享所有路由器的內(nèi)部狀態(tài)。但這并不意味著您不能使用路由器。假設(shè)您實(shí)際上可以使用鉤子(您使用的是 React 16.8+),您只需要一個(gè)包裝器。

import {
  useLocation,
  useNavigate,
  useParams
} from "react-router-dom";

function withRouter(Component) {
  function ComponentWithRouterProp(props) {
    let location = useLocation();
    let navigate = useNavigate();
    let params = useParams();
    return (
      <Component
        {...props}
        router={{ location, navigate, params }}
      />
    );
  }

  return ComponentWithRouterProp;
}

項(xiàng)目中完整例子

import { useRoutes } from 'react-router-dom'
import Backbone from '../layouts/Backbone'
import InfoPage from '../pages/InfoPage/index'
import DataCenterPage from '../pages/DataCenterPage/index'
import NoMatch from './NoMatch'

// 懶加載
const lazyLoad = (path: string) => {
    const Comp = React.lazy(() => import(`@page/${path}`))
    return (
        <React.Suspense fallback={<>加載中...</>}>
            <Comp />
        </React.Suspense>
    )
}

const routers = [
    {
        name: '項(xiàng)目',
        path: '/',
        element: <Backbone />,
        children: [
            // 僅匹配到父級路由時(shí)
            { index: true, fullPath: '/', element: lazyLoad('IndexPage') },
            {
                name: '首頁',
                path: 'index/*',
                fullPath: '/index',
                element: lazyLoad('IndexPage')
            },
            {
                name: '消息中心',
                path: 'info/',
                // element: lazyLoad('InfoPage'),
                element: <InfoPage />,
                children: [
                    { index: true, fullPath: '/info', element: lazyLoad('NoticeList') },
                    {
                        name: '公告',
                        path: 'noticeList',
                        fullPath: '/info/noticeList',
                        element: lazyLoad('NoticeList')
                    },
                    {
                        name: '幫助中心',
                        path: 'helpCenter',
                        fullPath: '/info/helpCenter',
                        element: lazyLoad('HelpCenter')
                    }
                ]
            },
            {
                name: '我要推廣',
                path: 'activityList/*',
                fullPath: '/activityList',
                element: lazyLoad('ActivityList')
            },
            {
                name: '數(shù)據(jù)中心',
                path: 'data/',
                // element: lazyLoad('InfoPage'),
                element: <DataCenterPage />,
                children: [
                    { index: true, fullPath: '/data', element: lazyLoad('Order') },
                    {
                        name: '訂單查詢',
                        path: 'order',
                        fullPath: '/data/order',
                        element: lazyLoad('Order')
                    },
                    {
                        name: '收益查詢',
                        path: 'reward',
                        fullPath: '/data/reward',
                        element: lazyLoad('Reward')
                    },
                    {
                        name: '結(jié)算查詢',
                        path: 'settlement',
                        fullPath: '/data/settlement',
                        element: lazyLoad('Settlement')
                    }
                ]
            }
        ]
    },
    {
        name: '公告詳情頁',
        // 動(dòng)態(tài)路由
        path: 'noticeDetail/:id',
        fullPath: '/noticeDetail',
        element: lazyLoad('NoticeDetail')
    },
    {
        name: '幫助中心詳情頁',
        path: 'helpCenterDetail/:fid/:sid',
        fullPath: '/helpCenterDetail',
        element: lazyLoad('HelpCenterDetail')
    },
    {
        name: '個(gè)人注冊頁',
        path: 'register/person',
        fullPath: '/register/person',
        element: lazyLoad('PersonBaseInfo')
    },
    {
        name: '404',
        path: '*',
        element: <NoMatch />
    }
]

const Index = () => useRoutes(routers)

export default Index

常用路由組件和hooks

完整API: https://reactrouter.com/docs/en/v6/api

組件名 作用 說明
<Routers> 一組路由 代替原有<Switch>,所有子路由都用基礎(chǔ)的Router children來表示
<Router> 基礎(chǔ)路由 Router是可以嵌套的,解決原有V5中嚴(yán)格模式
<Link> 導(dǎo)航組件 在實(shí)際頁面中跳轉(zhuǎn)使用
<Outlet/> 自適應(yīng)渲染組件 根據(jù)實(shí)際路由url自動(dòng)選擇組件,主要用于嵌套路由,類似Vue Router中的<router-view>
hooks名 作用 說明
useParams 返回當(dāng)前參數(shù) 根據(jù)路徑讀取參數(shù)
useNavigate 返回當(dāng)前路由 代替原有V5中的 useHistory
useOutlet 返回根據(jù)路由生成的element
useLocation 返回當(dāng)前的location 對象
useRoutes 同Routers組件一樣,只不過是在js中使用 代替react-router-config
useSearchParams 用來匹配URL中?后面的搜索參數(shù)

參考文獻(xiàn)

React Router網(wǎng)址: React Router

React Router文檔: https://reactrouter.com/docs/en/v6

完整API: https://reactrouter.com/docs/en/v6/api

react-router-dom: npm地址

參考博客:https://blog.csdn.net/weixin_40906515/article/details/104957712

https://zhuanlan.zhihu.com/p/191419879

http://www.itdecent.cn/p/03234215a90e

https://blog.csdn.net/zjjcchina/article/details/121921585

包大小分析網(wǎng)站: https://bundlephobia.com/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容