首頁 熱點 業界 科技快訊 數碼 電子消費 通信 前沿動態 電商

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

2023-03-26 14:10:24 來源 : 騰訊云

背景

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


(資料圖片僅供參考)

好家伙!這是我認知中的 React Router 嗎?

我2022年3月開發《聯機桌游合集》時,在用 6.2 版本,那時候 v6 跟 v5 v4 相比,API 已經發生了比較大的變化,但我認可這些變化。

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

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

1. 新增 createXXXXRouterAPI

1.1 介紹

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

createBrowserRoutercreateMemoryRoutercreateHashRouter

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

1.2 createXXXXRouter用法

必須結合一起使用。可以看到,它使用一個配置,定義路由。

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定義路由

當然,如果你喜歡用JSX語法定義路由,像一樣:

      } />  

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

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

2. 的變化

2.1 什么是 Data API?

當你使用createXXXXRouter時,你就可以使用 Data API。

說了這么多,什么是 Data API 呢?

其實就是允許你把「數據獲取邏輯」寫到路由定義中。每當路由切換到那里時,會自動獲取數據。

我們從的變化就可以看出,它新增了3個相關的屬性:

loaderactionerrorElement

2.2 loader 屬性

loader屬性傳入一個函數(允許是 async function),每次渲染「該路由對應的element」前執行函數。在「該路由對應的element」內,可以使用 hookuseLoaderData(下文會介紹)來獲取這個函數的返回值(通常是http請求的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 參數

loader屬性傳入的函數,允許有2個參數:

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

不要這個 request 參數行嗎?不行,因為如果你用window.location獲取的信息是當前最新的值,如果用戶快速的點擊按鈕,讓頁面路由到A,并立馬路由到B,這時候路由A對應的Route的loader獲取window.location時,就可能拿到錯誤的值。

注意,傳遞 request,還有個好處,它有個 request.signal,當用戶快速的點擊按鈕,讓頁面路由到A,并立馬路由到B,頁面A的loader的請求應該被取消掉,可以通過 signal 實現,如下:

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

2.2.2 loader 返回值

函數的返回值,將可以在element中通過hook useLoaderData(下文會介紹)來獲取。你返回什么,它就拿到什么。

但是 React Router 官方建議,返回一個 Web規范 中的 Fetch API 的 Response。

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

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

也可以通過 React Router 提供的 json 來構造:

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 中,可能校驗后需要重定向,React Router 不建議你用 useNavigation 完成,建議直接在 loader 中直接 return redirect,跳轉到新的網址。

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

2.2.3 loader 內拋出異常

如果數據獲取失敗,或者其它任何原因,你認為不能讓 Route 對應的 element 正常渲染了,你都可以在 loader 中 throw 異常。這時候,「errorElement」就會被渲染。

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 內通過 hook useRouteError來獲取到異常。

但是,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 方法,方便的構造個 Response:

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

2.3 element 屬性

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

2.3.1 內部可用 useLoaderData獲取 loader 返回值

注意,如果 loader 返回值是 Response,并且 Response 的 Content Type 是 application/json,React Router 內部會自動調用 .json() 方法,開發者不必寫 .json() 了。

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

2.3.2 內部可調用 useRouteLoaderData獲取 其它 Route 的 loader 返回值

React 組件可以嵌套,也可以嵌套,這時可以通過該 hook 獲取其它 的 loader 的返回值。當然,你需要提供 id。

定義路由時:

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

內部調用這個hook時:

const user = useRouteLoaderData("root");

2.4 errorElement 屬性

當 loader 內拋出異常,就不渲染它的 element 了,而是渲染它的 errorElement。

2.4.1 異常可以冒泡

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

2.4.2 內部可用 useRouteError獲取異常

在 errorElement 內,可用 useRouteError獲取異常。

const error = useRouteError();

2.4.3 內部可用 isRouteErrorResponse判斷異常類型

React Router 給了一個函數 isRouteErrorResponse,幫你在開發 errorElement 時,可以判斷當前異常是否是 Response 異常。因為 Response 異常 通常是開發者自己拋出的,是可以展示原因的(包括后端接口返回錯誤碼和錯誤提示文案,也可在這里處理)。其它異常,通常是未知的,就直接展示兜底的報錯文案即可。

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個參數:params 和 request。定義跟 loader 一樣。你可以 return 任何東西,同樣 React Router 建議你 return Response。你也可以 return redirect,實現重定向。在element內,你可以用hook useActionData獲取 action 返回值。(類似 useLoaderData

不同點在于,它們執行時機不同:

loader 是用戶通過 GET 導航至某路由時,執行的。action 是用戶提交 form 時,做 POST PUT DELETE 等操作時,執行的。

以前寫過

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

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

實在是忍不住了,想發表個人觀點:感覺沒用,屁用沒有

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

3. React Router 6.4 其它特性

當然,React Router 6.4 不僅有 Data API 這一個特性,它另一個重大更新是:Deferred Data: Deferred Data Guide。

再次忍不住發表個人觀點:為什么要加這個功能?是為了給 Data API “擦屁股”。

由于引入了 loader,內部有 API 請求,必然導致路由切換時,頁面需要時間去加載。加載時間長了怎么辦?需要展示 Loading 態。

解決方案一:不要在 loader 內發 API 請求,在 Route 對應的 element 里發請求,并展示 Loading 態。React Router 提供了貼心的 useFetcher,可以在element內發請求。解決方案二:針對 loader,提供一種配置方案,允許開發者定義 Loading 態。

React Router 這兩種方案都提供了。方案一就是 useFetcher。為了實現方案二,它引入了defer函數和組件。

3.1 defer 函數

在 loader 內使用,表明這個 loader 需要展示 Loading 態。如果 loader 返回了 defer,那么就會直接渲染 的 element。

 {    let book = await getBook(); // 這個不會展示 Loading 態,因為它被 await 了,會等它執行完并拿到數據    let reviews = getReviews(); // 這個會展示 Loading 態    return defer({      book, // 這是數據      reviews, // 這是 promise    });  }}  element={}/>;

3.2 組件

的 element 中使用,用于展示 Loading 態。需要結合使用,Loading 態展示在的 fallback 中。

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

{book.title}

{book.description}

}> />
);}

等 loader 加載完畢,就會展示 Await 的 children 里的內容了。

3.2.1 組件的 children 屬性

可以是函數,也可以是 React 組件。

如果是函數,Promise 結果就是參數:

  {(resolvedReviews) => }

如果是組件,內部通過useAsyncValue獲取 Promise 的結果。

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

個人觀點

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

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

打包測試

公共依賴:

"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( ,);

對比

打包物

體積(B)

增長的體積(B)

相對6.3增長倍數

React Router占總代碼比例

無 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%

結論

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

畢竟,我的《聯機桌游合集》里,沒有http請求。我只想用一個純粹的路由組件。而且6.4針對6.3的其它小feature,我也完全用不到。

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

寫在最后

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

標簽:

相關文章

最近更新