終究都要學 React 何不現在學呢? - React Router - Lazy Loading - (28)

前言

這一篇要來介紹 React Router 中很重要一個優化的部分,也就是 Lazy Loading,Lazy Loading 在實在開發上其實是非常重要的,它可以將我們的程式碼分割成不同的 chunk,然後在你需要或請求的時候才去載入相對應檔案,所以這一篇就會來介紹 Lazy Loading 的部分。

Lazy Loading

這邊我們將會使用這一份範例程式碼當作示範。

當你下載了前面的範例程式碼後,接著你可以在終端機輸入 npm run build 來看一下打包狀況,你會發現打包出來的檔案會是這樣的:

1
2
3
4
5
6
7
8
> [email protected] build
> vite build

vite v3.1.4 building for production...
✓ 39 modules transformed.
dist/index.html 0.44 KiB
dist/assets/index.f9c6795a.css 4.70 KiB / gzip: 1.53 KiB
dist/assets/index.50715e86.js 157.74 KiB / gzip: 51.15 KiB

我們可以看到上方中有一個 dist/assets/index.50715e86.js,這個檔案就是我們的整個專案打包編譯後的程式碼,而這個檔案大小為 157.74 KiB,雖然看起來好像沒有很大,但是如果我們的專案越來越龐大時,這個檔案也會跟著越來越大,那麼就會導致一些問題發生,其中最明顯的就是使用者在瀏覽網頁時,會因為網頁的載入時間太長而導致使用者離開,所以這時候就需要 Lazy Loading 來幫助我們。

當你使用了 Lazy Loading 之後,你會發現打包出來的檔案會類似變成這樣子

1
2
3
4
5
6
7
8
9
10
11
12
> [email protected] build
> vite build

vite v3.1.4 building for production...
✓ 40 modules transformed.
dist/index.html 0.44 KiB
dist/assets/Products.6a1dfb49.js 0.14 KiB / gzip: 0.14 KiB
dist/assets/App.2df53b30.js 0.11 KiB / gzip: 0.12 KiB
dist/assets/ToDoList.1f52c4c4.js 0.13 KiB / gzip: 0.13 KiB
dist/assets/Admin.868445da.js 0.13 KiB / gzip: 0.13 KiB
dist/assets/index.f9c6795a.css 4.70 KiB / gzip: 1.53 KiB
dist/assets/index.df8ef9bc.js 158.30 KiB / gzip: 51.47 KiB

我們可以看到整體檔案縮小了相當多,透過 Lazy Loading 我們可以將我們的程式碼分割成不同的 chunk,然後在你需要或請求的時候才去載入相對應檔案,所以這邊我們就來看一下怎麼使用 Lazy Loading 吧。

首先打開放在 routes 資料夾中的 index.js,接著我們可以看到這樣的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { useRoutes } from 'react-router-dom';

import App from '../App';
import ToDoList from '../ToDoList';
import Products from '../Products';
import Admin from '../Admin';

const routes = [
{
path: '/',
element: <App />,
},
{
path: '/todolist',
element: <ToDoList />,
},
{
path: '/products',
element: <Products />,
},
{
path: '/admin',
element: <Admin />,
}
];

export default () => useRoutes(routes);

接著我們要在最上方引入 lazy 這個函式

1
import { lazy } from 'react';

那麼 lazy 是什麼呢?簡單來說就是一個函式,這個函式可以讓我們在需要的時候才去載入相對應的程式碼,而不是一開始就載入所有程式碼,所以我們可以將 AppToDoListProductsAdmin 這四個元件都改成使用 lazy 來載入

1
2
3
4
5
6
import { lazy } from 'react';

const App = lazy(() => import('../App'));
const ToDoList = lazy(() => import('../ToDoList'));
const Products = lazy(() => import('../Products'));
const Admin = lazy(() => import('../Admin'));

接下來我們要在 App.js 中引入 Suspense 這個元件,這個元件可以讓我們在載入元件時顯示一些 Loading 的畫面,基本使用方式很簡單

1
<Suspense fallback={ Loading 元件 }>即將載入的元件<Suspense>

那為什麼要使用 Suspense 呢?主要原因是因為元件是使用 lazy 來載入的,所以在載入元件時會有一些延遲,所以我們可以使用 Suspense 來顯示一些 Loading 的畫面,讓使用者知道正在載入中,而不是一直等待載入完成,所以我們可以在 App.js 中加入 Suspense 來顯示 Loading 畫面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { StrictMode, Suspense } from 'react'
import { HashRouter, NavLink } from "react-router-dom";
import ReactDOM from 'react-dom/client'
import Router from './routes'
import './index.css';

ReactDOM.createRoot(document.getElementById('root')).render(
<StrictMode>
<HashRouter>
<nav className="px-5 flex items-center h-[60px] bg-indigo-500 text-white">
{/* ...略過 */}
</nav>
<Suspense fallback={<div>Loading...</div>}>
<Router />
</Suspense>
</HashRouter>
</StrictMode>
)

Suspensefallback 這個屬性是可以接收一個元件的,所以上方我們放入了一個 div,裡面放入 Loading... 的文字,這樣當我們載入元件時就會顯示 Loading... 的文字,而不是一直等待載入完成。

但是這邊要注意,若你沒有使用 Suspense 的話,是會出現以下的錯誤訊息

1
Uncaught Error: A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.

因此我們必須要使用 Suspense 來顯示 Loading 畫面,這樣才不會出現錯誤訊息。

那麼剛剛有提到 fallback 也可以寫入一個元件來使用,所以這邊就簡單示範一下,首先先建立一個 components/Loading.js 來放入一個 Loading 的元件

1
2
3
4
5
6
7
8
9
10
// components/Loading.jsx
const Loading = () => {
return (
<div className="flex justify-center items-center h-screen bg-indigo-500">
<img src="https://i.imgur.com/0HSGxif.gif" alt="" className="w-[150px] h-[150px] "/>
</div>
)
}

export default Loading;

接下來在 main.jsx 引入並將 Suspensefallback 替換成 Loading 元件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { StrictMode, Suspense } from 'react'
import { HashRouter, NavLink } from "react-router-dom";
import ReactDOM from 'react-dom/client'
import Router from './routes'
import Loading from './components/Loading';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')).render(
<StrictMode>
<HashRouter>
<nav className="px-5 flex items-center h-[60px] bg-indigo-500 text-white">
{/* ...略過 */}
</nav>
<Suspense fallback={ <Loading/> }>
<Router />
</Suspense>
</HashRouter>
</StrictMode>
)

接下來只要是初次載入的元件畫面都會出現 Loading 的畫面,而當你第二次載入時,就不會再出現 Loading 的畫面了。

那麼這一篇就先到這邊結束囉~

範例程式碼一樣上傳到這邊

後記

本文將會同步更新到我的部落格