首頁(yè) 熱點(diǎn) 業(yè)界 科技快訊 數(shù)碼 電子消費(fèi) 通信 前沿動(dòng)態(tài) 電商

當(dāng)前頭條:4k字介紹 React Router 6.4 超大變化:引入 Data API。你不純粹了!

2023-03-26 14:10:24 來(lái)源 : 騰訊云

背景

每次打開(kāi) React Router 官方文檔,都會(huì)有驚嚇,API又又又變了!這次看看有什么更新。


(資料圖片僅供參考)

好家伙!這是我認(rèn)知中的 React Router 嗎?

我2022年3月開(kāi)發(fā)《聯(lián)機(jī)桌游合集》時(shí),在用 6.2 版本,那時(shí)候 v6 跟 v5 v4 相比,API 已經(jīng)發(fā)生了比較大的變化,但我認(rèn)可這些變化。

現(xiàn)在看完 6.4 版本文檔, 我想吐槽。 我的核心觀點(diǎn)是:React Router 6.4 不再是純粹的路由組件了,它耦合了數(shù)據(jù)獲取邏輯。

下面本文 客觀介紹: React Router 6.4 引入的新功能 Data API,并在最后給 主觀結(jié)論

1. 新增 createXXXXRouterAPI

1.1 介紹

在 React Router 6.4 中,新增了 3 個(gè) createXXXXRouterAPI,用于支持 data API:

createBrowserRoutercreateMemoryRoutercreateHashRouter

也就是說(shuō),如果你不用這3個(gè)API,而是像v6.0-v6.3一樣,直接使用等下面幾個(gè)API,那么你享受不到 data API。

1.2 createXXXXRouter用法

必須結(jié)合一起使用。可以看到,它使用一個(gè)配置,定義路由。

import * as React from "react";import * as ReactDOM from "react-dom";import {  createBrowserRouter,  RouterProvider,} from "react-router-dom";const router = createBrowserRouter([  {    path: "/",    element: ,    children: [      {        path: "team",        element: ,      },    ],  },]);ReactDOM.createRoot(document.getElementById("root")).render(  );

1.3 也可用JSX定義路由

當(dāng)然,如果你喜歡用JSX語(yǔ)法定義路由,像一樣:

      } />  

React Router 6.4 也提供了JSX配置,參考createRoutesFromElements,它有另外一個(gè)名字叫createRoutesFromChildren。

const router = createBrowserRouter(  createRoutesFromElements(    }>      } />      } />      ));

2. 的變化

2.1 什么是 Data API?

當(dāng)你使用createXXXXRouter時(shí),你就可以使用 Data API。

說(shuō)了這么多,什么是 Data API 呢?

其實(shí)就是允許你把「數(shù)據(jù)獲取邏輯」寫(xiě)到路由定義中。每當(dāng)路由切換到那里時(shí),會(huì)自動(dòng)獲取數(shù)據(jù)。

我們從的變化就可以看出,它新增了3個(gè)相關(guān)的屬性:

loaderactionerrorElement

2.2 loader 屬性

loader屬性傳入一個(gè)函數(shù)(允許是 async function),每次渲染「該路由對(duì)應(yīng)的element」前執(zhí)行函數(shù)。在「該路由對(duì)應(yīng)的element」內(nèi),可以使用 hookuseLoaderData(下文會(huì)介紹)來(lái)獲取這個(gè)函數(shù)的返回值(通常是http請(qǐng)求的response)。

 {    // loaders can be async functions    const res = await fetch("/api/user.json", {      signal: request.signal,    });    const user = await res.json();    return user;  }}  element={}/>

2.2.1 loader 參數(shù)

loader屬性傳入的函數(shù),允許有2個(gè)參數(shù):

params: 如果Route中包含參數(shù)(例如path是/user/:userId,參數(shù)就是:userId,可以通過(guò)params.userId獲取到路由參數(shù)的值)。request: 是 Web 規(guī)范中,F(xiàn)etch API 的 Request,代表一個(gè)請(qǐng)求。注意:這里指的不是你在 loader 內(nèi)部發(fā)的 fetch 請(qǐng)求,而是當(dāng)用戶路由到當(dāng)前路徑時(shí),發(fā)出的“請(qǐng)求”(其實(shí)在Single-Page App中,router已經(jīng)攔截了這個(gè)真實(shí)的請(qǐng)求,只有Multi-Page App中才會(huì)有這個(gè)請(qǐng)求),這里是 React Router 6.4 為了方便開(kāi)發(fā)者獲取當(dāng)前路徑信息提供的參數(shù),他們按照 Web規(guī)范,制造了一個(gè)假的 request。你可以通過(guò) request方便的獲取當(dāng)前頁(yè)面的參數(shù):
 {    const url = new URL(request.url);    const searchTerm = url.searchParams.get("q");    return searchProducts(searchTerm);  }}/>

不要這個(gè) request 參數(shù)行嗎?不行,因?yàn)槿绻阌?code>window.location獲取的信息是當(dāng)前最新的值,如果用戶快速的點(diǎn)擊按鈕,讓頁(yè)面路由到A,并立馬路由到B,這時(shí)候路由A對(duì)應(yīng)的Route的loader獲取window.location時(shí),就可能拿到錯(cuò)誤的值。

注意,傳遞 request,還有個(gè)好處,它有個(gè) request.signal,當(dāng)用戶快速的點(diǎn)擊按鈕,讓頁(yè)面路由到A,并立馬路由到B,頁(yè)面A的loader的請(qǐng)求應(yīng)該被取消掉,可以通過(guò) signal 實(shí)現(xiàn),如下:

 {    return fetch("/api/teams.json", {      signal: request.signal,    });  }}/>

2.2.2 loader 返回值

函數(shù)的返回值,將可以在element中通過(guò)hook useLoaderData(下文會(huì)介紹)來(lái)獲取。你返回什么,它就拿到什么。

但是 React Router 官方建議,返回一個(gè) Web規(guī)范 中的 Fetch API 的 Response。

你可以直接 return fetch(url, config);,也可以自己構(gòu)造一個(gè)假的 Response:

function loader({ request, params }) {  const data = { some: "thing" };  return new Response(JSON.stringify(data), {    status: 200,    headers: {      "Content-Type": "application/json; utf-8",    },  });}//...

也可以通過(guò) React Router 提供的 json 來(lái)構(gòu)造:

import { json } from "react-router-dom";function loader({ request, params }) {  const data = { some: "thing" };  return json(data, { status: 200 });}//...

2.2.2.1 特殊返回值: redirect

在 loader 中,可能校驗(yàn)后需要重定向,React Router 不建議你用 useNavigation 完成,建議直接在 loader 中直接 return redirect,跳轉(zhuǎn)到新的網(wǎng)址。

import { redirect } from "react-router-dom";const loader = async () => {  const user = await getUser();  if (!user) {    return redirect("/login");  }};

2.2.3 loader 內(nèi)拋出異常

如果數(shù)據(jù)獲取失敗,或者其它任何原因,你認(rèn)為不能讓 Route 對(duì)應(yīng)的 element 正常渲染了,你都可以在 loader 中 throw 異常。這時(shí)候,「errorElement」就會(huì)被渲染。

function loader({ request, params }) {  const res = await fetch(`/api/properties/${params.id}`);  if (res.status === 404) {    throw new Response("Not Found", { status: 404 });  }  return res.json();}//...

注意:你可以拋出任何異常,都可以在 errorElement 內(nèi)通過(guò) hook useRouteError來(lái)獲取到異常。

但是,React Router 官方建議你 throw Response:

}  errorElement={}  loader={async ({ params }) => {    const res = await fetch(`/api/properties/${params.id}`);    if (res.status === 404) {      throw new Response("Not Found", { status: 404 });    }    const home = res.json();    return { home };  }}/>

你依然可以用 React Router 提供的 json 方法,方便的構(gòu)造個(gè) Response:

throw json(  { message: "email is required" },  { status: 400 },);

2.3 element 屬性

這個(gè)不是新屬性,即被匹配后,渲染的內(nèi)容。我想介紹它的變化:

2.3.1 內(nèi)部可用 useLoaderData獲取 loader 返回值

注意,如果 loader 返回值是 Response,并且 Response 的 Content Type 是 application/json,React Router 內(nèi)部會(huì)自動(dòng)調(diào)用 .json() 方法,開(kāi)發(fā)者不必寫(xiě) .json() 了。

function Albums() {  const albums = useLoaderData();  return 
{albums}
;}const router = createBrowserRouter([ { path: "/", loader: fetch("/api"), element: , },]);ReactDOM.createRoot(el).render( );

2.3.2 內(nèi)部可調(diào)用 useRouteLoaderData獲取 其它 Route 的 loader 返回值

React 組件可以嵌套,也可以嵌套,這時(shí)可以通過(guò)該 hook 獲取其它 的 loader 的返回值。當(dāng)然,你需要提供 id。

定義路由時(shí):

createBrowserRouter([  {    path: "/",    loader: () => fetchUser(),    element: ,    id: "root",    children: [      {        path: "jobs/:jobId",        loader: loadJob,        element: ,      },    ],  },]);

內(nèi)部調(diào)用這個(gè)hook時(shí):

const user = useRouteLoaderData("root");

2.4 errorElement 屬性

當(dāng) loader 內(nèi)拋出異常,就不渲染它的 element 了,而是渲染它的 errorElement。

2.4.1 異常可以冒泡

是可以嵌套的,每一層都可以定義 errorElement,異常發(fā)生后,會(huì)找到最近的 errorElement,并渲染它,然后停止冒泡。

2.4.2 內(nèi)部可用 useRouteError獲取異常

在 errorElement 內(nèi),可用 useRouteError獲取異常。

const error = useRouteError();

2.4.3 內(nèi)部可用 isRouteErrorResponse判斷異常類型

React Router 給了一個(gè)函數(shù) isRouteErrorResponse,幫你在開(kāi)發(fā) errorElement 時(shí),可以判斷當(dāng)前異常是否是 Response 異常。因?yàn)?Response 異常 通常是開(kāi)發(fā)者自己拋出的,是可以展示原因的(包括后端接口返回錯(cuò)誤碼和錯(cuò)誤提示文案,也可在這里處理)。其它異常,通常是未知的,就直接展示兜底的報(bào)錯(cuò)文案即可。

function RootBoundary() {  const error = useRouteError();  if (isRouteErrorResponse(error)) {    if (error.status === 404) {      return 
This page doesn"t exist!
; } if (error.status === 503) { return
Looks like our API is down
; } } return
Something went wrong
;}

2.5 action 屬性

它很像 laoder,你看:

它也有2個(gè)參數(shù):params 和 request。定義跟 loader 一樣。你可以 return 任何東西,同樣 React Router 建議你 return Response。你也可以 return redirect,實(shí)現(xiàn)重定向。在element內(nèi),你可以用hook useActionData獲取 action 返回值。(類似 useLoaderData

不同點(diǎn)在于,它們執(zhí)行時(shí)機(jī)不同:

loader 是用戶通過(guò) GET 導(dǎo)航至某路由時(shí),執(zhí)行的。action 是用戶提交 form 時(shí),做 POST PUT DELETE 等操作時(shí),執(zhí)行的。

以前寫(xiě)過(guò)

的都知道,它有 action 和 method 參數(shù),在以前,提交表單也是在瀏覽器內(nèi)做了一次改變URL的操作。使用React后,幾乎沒(méi)人這么做,大家都是AJAX或Fetch提交表單了。

現(xiàn)在,React Router 提供了 組件,并給 組件增加了 action屬性,讓提交表單也變成一次路由。

實(shí)在是忍不住了,想發(fā)表個(gè)人觀點(diǎn):感覺(jué)沒(méi)用,屁用沒(méi)有

如果你想了解 Route 的 action 屬性,一定要看 React Router Form,注意 Form 里也有個(gè) action 屬性,不要搞混了。

3. React Router 6.4 其它特性

當(dāng)然,React Router 6.4 不僅有 Data API 這一個(gè)特性,它另一個(gè)重大更新是:Deferred Data: Deferred Data Guide。

再次忍不住發(fā)表個(gè)人觀點(diǎn):為什么要加這個(gè)功能?是為了給 Data API “擦屁股”。

由于引入了 loader,內(nèi)部有 API 請(qǐng)求,必然導(dǎo)致路由切換時(shí),頁(yè)面需要時(shí)間去加載。加載時(shí)間長(zhǎng)了怎么辦?需要展示 Loading 態(tài)。

解決方案一:不要在 loader 內(nèi)發(fā) API 請(qǐng)求,在 Route 對(duì)應(yīng)的 element 里發(fā)請(qǐng)求,并展示 Loading 態(tài)。React Router 提供了貼心的 useFetcher,可以在element內(nèi)發(fā)請(qǐng)求。解決方案二:針對(duì) loader,提供一種配置方案,允許開(kāi)發(fā)者定義 Loading 態(tài)。

React Router 這兩種方案都提供了。方案一就是 useFetcher。為了實(shí)現(xiàn)方案二,它引入了defer函數(shù)和組件。

3.1 defer 函數(shù)

在 loader 內(nèi)使用,表明這個(gè) loader 需要展示 Loading 態(tài)。如果 loader 返回了 defer,那么就會(huì)直接渲染 的 element。

 {    let book = await getBook(); // 這個(gè)不會(huì)展示 Loading 態(tài),因?yàn)樗?await 了,會(huì)等它執(zhí)行完并拿到數(shù)據(jù)    let reviews = getReviews(); // 這個(gè)會(huì)展示 Loading 態(tài)    return defer({      book, // 這是數(shù)據(jù)      reviews, // 這是 promise    });  }}  element={}/>;

3.2 組件

的 element 中使用,用于展示 Loading 態(tài)。需要結(jié)合使用,Loading 態(tài)展示在的 fallback 中。

function Book() {  const {    book,    reviews, // this is the same promise  } = useLoaderData();  return (    

{book.title}

{book.description}

}> />
);}

等 loader 加載完畢,就會(huì)展示 Await 的 children 里的內(nèi)容了。

3.2.1 組件的 children 屬性

可以是函數(shù),也可以是 React 組件。

如果是函數(shù),Promise 結(jié)果就是參數(shù):

  {(resolvedReviews) => }

如果是組件,內(nèi)部通過(guò)useAsyncValue獲取 Promise 的結(jié)果。

  ;function Reviews() {  const resolvedReviews = useAsyncValue();  return 
{/* ... */}
;}

個(gè)人觀點(diǎn)

重申我的核心觀點(diǎn):React Router 6.4 不再是純粹的路由組件了,它耦合了數(shù)據(jù)獲取邏輯。

如果一個(gè)龐大項(xiàng)目,一些數(shù)據(jù)獲取邏輯在 Router 里,一些數(shù)據(jù)獲取邏輯在內(nèi)部組件。這不利于項(xiàng)目維護(hù)。React Router 6.4 為了加個(gè) Data API,增加了很多代碼。v6.4 打包UMD production.min.js 體積(16.1KB) 是 v6.3 打包UMD production.min.js(6.75KB) 體積的 2.4 倍!

打包測(cè)試

公共依賴:

"react": "^18.2.0","react-dom": "^18.2.0","react-scripts": "5.0.1",

不引用 React Router

下面代碼打包后,141199 B。

import React from "react";import ReactDOM from "react-dom/client";const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);root.render(      
,);

React Router v6.3

下面代碼打包后,150266 B。

import React from "react";import ReactDOM from "react-dom/client";import { BrowserRouter, Routes, Route } from "react-router-dom";const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);root.render(                    } />            ,);

React Router v6.4 不用 Data API

代碼跟上面一致,159758 B。

React Router v6.4 使用 Data API

下面代碼打包后,196040 B。

import React from "react";import ReactDOM from "react-dom/client";import {  createBrowserRouter,  RouterProvider,} from "react-router-dom";import "./index.css";const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);const router = createBrowserRouter([  {    index: true,    element: 
, },]);root.render( ,);

對(duì)比

打包物

體積(B)

增長(zhǎng)的體積(B)

相對(duì)6.3增長(zhǎng)倍數(shù)

React Router占總代碼比例

無(wú) React Router

141199

0

-

-

React Router 6.3

150266

9067

1倍

6%

React Router 6.4 不用 Data API

159758

18559

2.05倍

12%

React Router 6.4 使用 Data API

196040

54841

6.05倍

28%

結(jié)論

最終,我愿意使用 react-router-dom=~6.3.0,即不更新到 6.4,永遠(yuǎn)使用 6.3.x。

畢竟,我的《聯(lián)機(jī)桌游合集》里,沒(méi)有http請(qǐng)求。我只想用一個(gè)純粹的路由組件。而且6.4針對(duì)6.3的其它小feature,我也完全用不到。

React Router 最新版 文檔鏈接: https://reactrouter.com/en/mainReact Router 6.3.0 文檔鏈接: https://reactrouter.com/en/v6.3.0

寫(xiě)在最后

我是HullQin,公眾號(hào)線下聚會(huì)游戲的作者(歡迎關(guān)注我,交個(gè)朋友)。轉(zhuǎn)發(fā)本文前需獲得作者HullQin授權(quán)。我獨(dú)立開(kāi)發(fā)了《聯(lián)機(jī)桌游合集》,是個(gè)網(wǎng)頁(yè),可以很方便的跟朋友聯(lián)機(jī)玩UNO、斗地主、五子棋、飛行棋、一夜狼、象棋、德國(guó)心臟病、達(dá)芬奇密碼等游戲,不收費(fèi)無(wú)廣告。還開(kāi)發(fā)了《Dice Crush》參加Game Jam 2022。喜歡可以關(guān)注我噢~我有空了會(huì)分享做游戲的相關(guān)技術(shù),會(huì)在這個(gè)專欄里分享:《教你做小游戲》。

標(biāo)簽:

相關(guān)文章

最近更新