跳至主要内容

3 篇文章 含有標籤「Performance Optimization」

檢視所有標籤

React 效能優化入門教學筆記 | 學習筆記

· 閱讀時間約 6 分鐘
kdchang

前言

隨著前端應用日益龐大,單頁應用(SPA)在初次載入時常面臨 JavaScript 檔案過大、載入時間過久的問題,導致使用者等待時間過長、效能下降。為了解決這個問題,React 與現代建構工具(如 Webpack、Vite)提供了 Code Splitting(程式碼分割)與 Lazy Loading(延遲載入)兩種策略,協助開發者更有效地管理與優化應用程式的載入流程。

程式碼分割(Code Splitting)和惰性載入(Lazy Loading)都是用來優化網頁效能的方法,它們都旨在減少初始加載時間,但實現方式和目標略有不同。 程式碼分割是將程式碼分割成多個較小的塊,而惰性載入則是在需要時才加載這些塊。

一、程式碼分割(Code Splitting)

概念:

程式碼分割是將一個大型的 JavaScript 應用程式分割成多個較小的、獨立的塊,每個塊包含應用程式的一部分程式碼。 這些塊通常是根據路由、元件或功能來分割的。

目標:

主要目標是減少應用程式的初始加載時間,通過只加載使用者當前需要的程式碼塊,而不是一次性加載所有程式碼。

實現方式:

程式碼分割通常使用打包工具(如 Webpack、Rollup 等)和動態import()語法來實現。

使用時機:

在編譯時(build time)就進行分割。

二、惰性載入(Lazy Loading)

概念:

惰性載入是指在需要的時候才加載程式碼,而不是在應用程式初始化時就加載所有程式碼。

目標:

減少應用程式的初始加載時間,特別是對於大型應用程式或元件。

實現方式:

惰性載入通常使用 React.lazy 和 Suspense 元件來實現,也可以配合程式碼分割一起使用。

使用時機:

在執行時(runtime)才加載,通常是當使用者訪問某個路由、觸發某個事件或需要顯示某個元件時。

差異總結

特性程式碼分割 Code Splitting惰性載入 Lazy Loading
概念將程式碼分割成多個塊在需要時才加載程式碼
目標減少初始加載時間,優化效能減少初始加載時間,優化效能
實現方式打包工具,dynamic import()React.lazy, Suspense, dynamic import()
時機編譯時執行時
關聯性程式碼分割是惰性載入的基礎,惰性載入可以利用程式碼分割的結果。

重點摘要

  • Code Splitting(程式碼分割)

    • 是一種將整個應用程式切割成多個檔案的技術
    • 通常由 Webpack、Rollup 等建構工具自動處理
    • 可應用於 route-based 分割、component-based 分割等情境
    • 不代表一定是延遲載入,僅是結構上的切割
  • Lazy Loading(延遲載入)

    • 是一種執行時載入程式碼的策略
    • 常與 import() 搭配,直到實際使用時才載入
    • 通常透過 React.lazySuspense 實現元件的懶載入
    • 是 Code Splitting 的使用方式之一
  • 兩者關係

    • Code Splitting 是靜態建構階段的優化策略
    • Lazy Loading 是執行階段的載入行為
    • Lazy Loading 必須建立在已做 Code Splitting 的前提上
  • 效益

    • 減少主程式 bundle 的大小
    • 提升首次載入速度(First Contentful Paint)
    • 延遲不必要的資源載入,節省頻寬與記憶體

實際範例

範例一:傳統未分割的情況(單一 bundle)

// App.js
import HomePage from './HomePage';
import Dashboard from './Dashboard';

function App() {
return (
<>
<HomePage />
<Dashboard />
</>
);
}

這樣寫會導致 HomePage 和 Dashboard 在應用一開始就被載入,無論使用者有沒有看到這些元件。


範例二:使用 React.lazy 實現 Lazy Loading 與 Code Splitting

// App.js
import React, { Suspense } from 'react';

// Lazy Loading:只有在渲染時才動態 import
const HomePage = React.lazy(() => import('./HomePage'));
const Dashboard = React.lazy(() => import('./Dashboard'));

function App() {
return (
<Suspense fallback={<div>載入中...</div>}>
<HomePage />
<Dashboard />
</Suspense>
);
}

使用 React.lazy() 搭配 import() 會讓 Webpack 將這些元件建立為獨立的 chunk。 真正渲染時(如使用者切換頁面),才會觸發載入行為,減少初始 bundle 體積。


範例三:Route-based Code Splitting(React Router)

// AppRouter.js
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

export default function AppRouter() {
return (
<Router>
<Suspense fallback={<div>頁面載入中...</div>}>
<Switch>
<Route path="/home" component={Home} />
<Route path="/dashboard" component={Dashboard} />
</Switch>
</Suspense>
</Router>
);
}

使用路由為單位切割頁面元件,是最常見的 Code Splitting 實務做法。


範例四:動態 import 實作非元件的延遲載入

// utils.js
export function heavyCalculation(input) {
// 假設這段計算非常耗時
return input ** 10;
}
// App.js
function handleClick() {
import('./utils').then(({ heavyCalculation }) => {
const result = heavyCalculation(5);
console.log(result);
});
}

在某些不需要立即執行的邏輯或大型工具函式庫,也可以透過 import() 動態載入來延遲其成本。


常見問題與補充

Q1:Code Splitting 是自動的嗎?

  • 大部分情況下需要手動設計入口點(如 React.lazyimport()),Webpack 才會建立分離的 chunk。

Q2:只有使用 React.lazy 才能 Lazy Load 嗎?

  • 不一定,import() 是底層機制,也可配合其他框架(Vue、Svelte)或工具(React Loadable)使用。

Q3:懶載入的元件可以預載嗎?

  • 可以,透過 import().then() 觸發一次即可放進瀏覽器快取,達到「預熱」效果。

總結

在前端應用越來越大型化的今天,掌握 Code Splitting 與 Lazy Loading 的差異與使用場景,已成為每位前端工程師的必備技能。Code Splitting 解決的是「結構上的模組分離」,Lazy Loading 則是「載入時機的延後」。兩者密不可分,但用法與思考層次不同。

實務上可先針對頁面級路由進行分割,再進一步優化元件級的載入、工具模組載入時機,逐步降低初始 bundle 體積,提升網站效能與使用者體驗。

參考文件

  1. React | 為太龐大的程式碼做 Lazy Loading 和 Code Splitting

React 效能優化入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

React 作為現代前端開發的主流函式庫之一,強調 UI 的組件化與狀態驅動式渲染。然而,隨著應用規模擴大與資料變得動態頻繁,React 應用可能出現重新渲染過多、載入過慢或記憶體占用過高等問題,影響使用者體驗與開發效率。為此,瞭解與掌握 React 的效能優化技巧,成為中高階開發者的重要功課。

本篇筆記將介紹 React 效能優化的核心原則與常見實作方式,搭配簡單的程式碼範例說明實際操作,協助你建立清晰的優化思維與實作經驗。


重點摘要

  • 避免不必要的重新渲染

    • 使用 React.memo 包裹純函式組件
    • 適當使用 useMemouseCallback 記憶運算結果或函式引用
  • Lazy loading(Code Splitting)

    • 使用 React.lazySuspense 實現組件按需載入
  • 列表渲染優化

    • 提供穩定的 key,避免 diff 錯誤導致重繪
    • 處理大量資料時可結合虛擬化工具(如 react-window
  • 狀態管理與邏輯分離

    • 將全域狀態與 UI 狀態分離,減少重渲染範圍
    • 減少 props 傳遞鏈,避免深層組件無謂更新
  • 避免 inline 宣告與函式

    • 每次 render 都會產生新函式或物件,導致子組件重新渲染
  • 效能分析與工具

    • 使用 React DevTools 的 Profiler 模組分析 render 開銷
    • 善用 Chrome DevTools、Lighthouse 等協助調校效能

實際範例

1. 避免不必要的渲染:使用 React.memo

// 子元件
const TodoItem = React.memo(function TodoItem({ todo, onToggle }) {
console.log('Render:', todo.text);
return (
<li>
<input type="checkbox" checked={todo.completed} onChange={() => onToggle(todo.id)} />
{todo.text}
</li>
);
});

若未使用 React.memo,即使 todo 資料未變,只要父層重新 render,TodoItem 就會跟著重新 render。使用 React.memo 可避免這種不必要的重新渲染。


2. 函式記憶:使用 useCallback

const onToggle = useCallback((id) => {
setTodos((prev) =>
prev.map((todo) => (todo.id === id ? { ...todo, completed: !todo.completed } : todo))
);
}, []);

如果 onToggle 每次 render 都重新宣告,會導致 React.memo 判斷 props 改變,從而重新渲染子元件。使用 useCallback 可以保留函式參考的一致性。


3. 虛擬滾動列表:使用 react-window

import { FixedSizeList as List } from 'react-window';

const Row = ({ index, style }) => <div style={style}>Row {index}</div>;

const MyList = () => (
<List height={300} itemCount={1000} itemSize={35} width={300}>
{Row}
</List>
);

react-window 提供虛擬滾動的能力,只 render 可視範圍內的項目,大幅減少 DOM 結點,提高大數據列表效能。


4. 懶載入元件:使用 React.lazy

import React, { Suspense } from 'react';

const Chart = React.lazy(() => import('./Chart'));

function Dashboard() {
return (
<Suspense fallback={<div>Loading chart...</div>}>
<Chart />
</Suspense>
);
}

將大型組件分割成懶載入模組,可避免初次載入體積過大,提升頁面初始加載速度。


5. 使用 Profiler 分析效能瓶頸

React DevTools 提供 Profiler 模組,可追蹤各元件 render 時間與次數,有助於識別過度渲染或效能低落的元件。

import { Profiler } from 'react';

<Profiler
id="TodoList"
onRender={(id, phase, actualDuration) => {
console.log(`${id} rendered in ${actualDuration}ms`);
}}
>
<TodoList todos={todos} />
</Profiler>;

結語

React 效能優化並非一蹴可幾,而是需隨著應用規模與需求不斷調整與改善的過程。透過理解 Virtual DOM 的運作原理、掌握各種 Hook 的特性,以及活用分析工具,我們可以更有策略地針對效能瓶頸逐步優化,打造流暢且可維護的使用者體驗。

建議從小型優化(如 React.memouseCallback)著手,並逐步引入懶載入與虛擬化等進階技巧,讓 React 應用能夠隨著功能擴展持續保持高效能。

如果你對特定效能問題有興趣,例如圖片載入優化、CSR vs SSR 效能比較等,也可以再深入探討不同的進階主題。

React 效能優化 SOP 檢核清單入門教學筆記 | 學習筆記

· 閱讀時間約 6 分鐘
kdchang

前言

在大型單頁應用(SPA)與複雜互動式介面中,效能瓶頸常常來自不必要的重新渲染、大型 bundle 導致的載入緩慢,以及過度操作 DOM 所造成的卡頓。建立一份標準化的「效能優化檢核清單」(SOP,Standard Operating Procedure),能夠在開發流程中明確指出應檢查的重點、落實最佳實踐,並透過持續監控與回饋,進一步強化團隊的效能意識與程式品質。

本篇筆記將依照從「程式撰寫到部署」的不同階段,提出具體的檢核項目,並搭配最常見的 React 效能優化技術範例,協助你快速掌握如何在日常開發與 Code Review 中落實效能優化。


重點摘要

  • 一、避免不必要的重新渲染

    • 使用 React.memo 包裹純函式元件
    • 針對函式與物件 props,使用 useCallbackuseMemo 進行記憶
    • 避免 JSX inline 宣告函式或物件
  • 二、State 管理與元件分離

    • 下放 state 至影響範圍最小的元件
    • UI 狀態(開關、Modal 等)與業務資料分離
    • 避免全域 context 過度包覆,導致大範圍 re-render
  • 三、列表與大量資料渲染優化

    • 確保 key 穩定(使用唯一 id,非 index)
    • 採用虛擬滾動(react-windowreact-virtualized
    • 分頁或懶加載機制
  • 四、Code Splitting 與懶載入

    • 使用 React.lazy + Suspense 分割大型元件
    • 路由層級拆分,動態 import()
    • 圖片與第三方資源延遲加載
  • 五、效能分析與監控工具

    • React DevTools Profiler:分析元件 render 次數與耗時
    • Lighthouse / Web Vitals:追蹤 FCP、LCP、TTFB 等指標
    • Bundle 分析(Webpack Bundle Analyzer、Source Map Explorer)
  • 六、CI/Code Review 效能檢查

    • 將檢核清單納入 Pull Request 模板
    • 自動化檢測 bundle size 變化
    • 定期性能測試腳本(Cypress、Playwright + Lighthouse)

實際範例

範例一:避免不必要的重新渲染

// ChildComponent.jsx
import React from 'react';

function ChildComponent({ data, onClick }) {
console.log('ChildComponent render');
return <div onClick={onClick}>{data.text}</div>;
}

export default React.memo(ChildComponent);
// ParentComponent.jsx
import React, { useState, useCallback, useMemo } from 'react';
import ChildComponent from './ChildComponent';

export default function ParentComponent({ initialData }) {
const [count, setCount] = useState(0);

// useMemo 記憶 data 物件,避免因父組件重新 render 而改變 reference
const data = useMemo(
() => ({
text: initialData,
}),
[initialData]
);

// useCallback 記憶函式,不會因為 count 變化而重新建立
const handleClick = useCallback(() => {
setCount((c) => c + 1);
}, []);

return (
<div>
<p>點擊次數:{count}</p>
<ChildComponent data={data} onClick={handleClick} />
</div>
);
}

檢核點

  • ChildComponent 是否用 React.memo 包裹?
  • data 物件是否用 useMemo
  • onClick 是否用 useCallback

範例二:列表虛擬化

// ListView.jsx
import React from 'react';
import { FixedSizeList as List } from 'react-window';

const Row = React.memo(({ index, style }) => <div style={style}>列表項目 #{index}</div>);

export default function ListView() {
return (
<List height={400} itemCount={10000} itemSize={35} width={'100%'}>
{Row}
</List>
);
}

檢核點

  • 是否針對長列表導入虛擬化?
  • itemSize 與 height 設定是否合理?

範例三:Code Splitting 與懶載入

// routes.jsx
import React, { Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const Home = React.lazy(() => import('./pages/Home'));
const Dashboard = React.lazy(() => import('./pages/Dashboard'));

export default function AppRouter() {
return (
<Router>
<Suspense fallback={<div>載入中...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/dashboard" component={Dashboard} />
</Switch>
</Suspense>
</Router>
);
}

檢核點

  • 是否有針對路由或大型元件進行懶載入?
  • fallback UI 是否友善?

範例四:效能分析

import React, { Profiler } from 'react';
import TodoList from './TodoList';

function onRenderCallback(id, phase, actualDuration) {
console.log(`${id} ${phase} 耗時:${actualDuration.toFixed(2)}ms`);
}

export default function App() {
return (
<Profiler id="TodoList" onRender={onRenderCallback}>
<TodoList />
</Profiler>
);
}

檢核點

  • 是否使用 Profiler 區隔並記錄核心元件耗時?
  • 是否定期檢視開發者工具數據?

總結

以上檢核清單涵蓋了從程式撰寫、元件切分,到效能分析與持續監控的各個面向。建議將此清單整合至 Pull Request 模板中,並在團隊中推廣效能優化文化。持續在日常開發中落實這些檢查,能確保應用在功能增長的同時仍保持流暢的使用者體驗,並降低潛在的性能退化風險。若需將本文轉為 Markdown、PDF 或 Notion 模板,歡迎隨時提出。

補充:React 效能優化 SOP 檢核清單

一、避免不必要的重新渲染

  • 是否使用 React.memo 包裹純函式元件?
  • 是否有使用 useCallback 記憶傳遞的函式 props?
  • 是否有使用 useMemo 記憶計算結果,避免重複計算?
  • 是否避免在 JSX 中直接宣告函式或物件(例如 inline style)?

二、State 管理與元件分離

  • 是否將狀態下放至最小影響範圍的元件中?
  • 是否避免使用不必要的 lifting state up?
  • 是否將 UI 狀態(如開關、hover 狀態)與全域狀態分離?

三、Props 傳遞與結構優化

  • 是否控制 props 深層傳遞導致的層層 re-render?
  • 是否 props 結構穩定、可預期?(避免 object/array 每次都變)

四、渲染大量資料時的處理

  • 是否針對長列表使用虛擬化工具(如 react-window, react-virtualized)?
  • 是否有合理使用 key(且為穩定值,例如 id 而非 index)?

五、資源載入與 Code Splitting

  • 是否使用 React.lazy + Suspense 實現元件懶載入?
  • 是否進行 route-based code splitting(使用動態 import)?
  • 是否有壓縮圖片、延遲圖片載入(lazy loading)?

六、效能監控與分析

  • 是否使用 React DevTools Profiler 檢查 render 頻率與時間?
  • 是否分析過 Lighthouse / Web Vitals 的效能指標?
  • 是否檢查 Bundle Size(Webpack 分析工具、SourceMap Explorer 等)?

七、避免常見陷阱

  • 是否避免每次 render 都新建匿名函式?
  • 是否避免重複 render 同一資料來源?
  • 是否避免過度依賴 context 導致全頁重 render?

八、開發階段優化習慣

  • 是否將效能優化納入 Code Review 檢查點?
  • 是否每個大型新元件都確認是否會引起不必要渲染?
  • 是否測試過主流程在弱網或低效能設備上的表現?