React 進階開發技巧:10 個資深工程師必學的效能優化與狀態管理方法

Published on: | Last updated:

好,呃...今天我們來聊聊 React。不是那種「什麼是 component」的入門東西,我想聊的是那種...嗯,大家都說是「資深」或「進階」的技巧。但說真的,我自己是覺得,所謂的「進階」不是你多會背幾個 API,或是知道什麼很炫炮的 library,重點其實是「判斷力」。你知道什麼時候該用,什麼時候不該用,還有用了之後可能會踩到什麼坑。這才是價值所在。而不是像清單一樣把十個東西列出來,那沒什麼意思。

很多文章都會給你一個列表,一、二、三、四...看到十。但他們很少告訴你這些技術之間的關聯,或是用了 A 會不會讓 B 更難搞。所以,今天我想用一個比較...嗯,像是自言自語的方式,把這些東西串起來聊聊,聊聊它們背後的「為什麼」,而不只是「怎麼做」。

所以結論是啥?一句話說完

TL;DR:真正拉開差距的不是你會不會用這些技巧,而是你懂不懂在「效能」、「可維護性」和「開發速度」這三個點之間做取捨。沒什麼銀彈,全都是 trade-off。

第一個大魔王:應用程式怎麼變快?

這大概是每個 React project 長大後都會遇到的問題。一開始都很快,使用者一多、功能一加,不知道為什麼,網站就開始變得很...很笨重。這邊有兩組核心武器可以處理這件事。

第一組是針對「載入速度」的,就是使用者第一次打開網站的那個感覺。最經典的就是 Code Splitting。

你想想看,你進去一個大賣場,只是想買瓶牛奶,結果你被迫把整個賣場所有商品,連同冷凍庫裡的冰棒跟園藝區的鏟子,全部都先下載到你的腦袋裡...這不是很蠢嗎?Code Splitting 就是讓你只拿那瓶牛奶就好。

在 React 裡,就是用 React.lazySuspense 這對組合技。簡單說,就是告訴 React:「嘿,這個 component 先不用急著載入,等真的要顯示它的時候再說。」然後用 Suspense 告訴它在等待的時候要顯示什麼,比如說一個轉圈圈的 loading 圖示。


import React, { Suspense, lazy } from 'react';

// 原本是 import UserProfile from './UserProfile';
// 現在改成這樣
const UserProfile = lazy(() => import('./UserProfile'));
const AdminDashboard = lazy(() => import('./AdminDashboard'));

function App({ user }) {
  return (
    <div>
      <h1>歡迎回來!</h1>
      {/* Suspense 可以包住多個 lazy component */}
      <Suspense fallback={<div>讀取中,請稍候...</div>}>
        {/* 只有當 user 存在時,才會去下載 UserProfile 的程式碼 */}
        {user && <UserProfile />}

        {/* 只有管理員才需要載入這個超大的後台面板 */}
        {user.isAdmin && <AdminDashboard />}
      </Suspense>
    </div>
  );
}

我自己是覺得,fallback 的體驗很重要。不要只是一個簡單的「讀取中...」,如果可以,做一個跟即將載入的內容長得很像的「骨架屏 (Skeleton Screen)」,使用者的體感會好非常多。

Code Splitting 載入示意,左邊是已載入的主畫面,右邊是用骨架屏呈現的延遲載入區塊。
Code Splitting 載入示意,左邊是已載入的主畫面,右邊是用骨架屏呈現的延遲載入區塊。

第二個效能議題:怎麼別讓它一直重畫?

好,載入變快了,但用起來還是卡卡的。這通常就是 re-render 的問題。React 有時候...呃,有點太敏感了。一有風吹草動,它就把整個畫面都重新畫一次,即使很多地方根本沒變。這時候就要靠「記憶化 (Memoization)」。

你可以把它想像成一個很會做筆記的學生。老師問一個很難的數學題,他算出來之後就把答案寫在筆記本上。下次老師再問一模一樣的問題,他就不用再算一次,直接翻筆記本就好。

在 React 裡面,這本筆記本就是 React.memouseMemouseCallback

  • React.memo:用來包住整個 Component。如果傳進來的 props 都沒變,它就直接拿上次畫好的結果出來用,連 render function 都不會跑。
  • useMemo:用來記住「計算結果」。比如說你有個超複雜的運算,或是要對一個超大的陣列做篩選排序,你就可以用 useMemo 把算出來的結果存起來。
  • useCallback:用來記住「函式本身」。這聽起來有點怪,函式就函式,記住要幹嘛?因為在 JavaScript 裡,每次 re-render,Component 裡面的函式都會被「重新創建」一次,即使長得一模一樣,但在記憶體裡是不同的人。如果你把這個函式當成 prop 傳給一個有用 React.memo 包住的子元件,那子元件就會覺得「欸,prop 變了!」,然後就破功,又 re-render 了。useCallback 就是為了解決這個問題。

不過呢,這東西是雙面刃。千萬、千萬不要沒事就把所有東西都包一層 memo。因為做筆記本身也是要花時間花力氣的(佔用記憶體、做比較),如果你記的都是一些無關緊要的小事,反而更浪費資源。只有在「props 真的很少變動」而且「元件本身 re-render 很昂貴」的時候,用它才有意義。

讓程式碼變乾淨的魔法:Custom Hooks

OK,效能搞定了,再來是程式碼的「可維護性」。我覺得這點是區分中手跟高手的關鍵。你的程式碼是不是幾個月後你自己還看得懂?別人接手會不會想罵髒話?

Custom Hooks 絕對是 React 史上最偉大的發明之一,沒有之一。它解決了一個長久以來的痛點:怎麼在不同的 Component 之間共享「有狀態的邏輯 (stateful logic)」。以前我們要用 HOC (Higher-Order Components) 或 Render Props,那寫起來真的是...嗯,很痛苦,會產生所謂的「Wrapper Hell」,一層包一層,像俄羅斯娃娃一樣。

Custom Hook 說穿了,就是一個函式,它的名字必須是 `use` 開頭,然後它裡面可以呼叫其他的 Hooks,像是 `useState`、`useEffect`。就這樣。

比如說,你很多頁面都需要去 call API 拿資料,然後處理 loading 跟 error 狀態。你就可以把這整套邏輯抽出來,變成一個 useFetch 的 hook。


import { useState, useEffect } from 'react';

// 這就是一個 Custom Hook
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // 每次 url 變了,就重新抓一次資料
    setLoading(true);
    setError(null);
    
    fetch(url)
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(error => {
        setError(error);
        setLoading(false);
      });
      
  }, [url]); // 依賴 url,當 url 改變時,這個 effect 會重新執行

  // 把狀態跟結果回傳出去
  return { data, loading, error };
}

// 在你的 Component 裡就可以這樣用,超乾淨
function MyUserProfile({ userId }) {
  const { data: user, loading, error } = useFetch(`/api/users/${userId}`);

  if (loading) return <p>讀取中...</p>;
  if (error) return <p>糟糕,出錯了:{error.message}</p>;

  return <div>使用者名稱:{user.name}</div>;
}

你看,Component 裡面變得超乾淨,它只關心「拿到資料要幹嘛」,完全不用管「怎麼拿到資料」的那些髒活。那些髒活全都封裝在 useFetch 裡面了。你可以到處重複使用這個 hook,超實用。

Custom Hooks 就像是可重複利用的邏輯積木。
Custom Hooks 就像是可重複利用的邏輯積木。

最頭痛的問題:狀態管理到底用哪個?

這大概是 React 社群吵最兇的議題了。當你的應用程式變複雜,元件之間的層級變深,單純靠 props 把資料傳下去會變成一場災難,就是所謂的 "Prop Drilling"。想像一下,你要把一個資料從阿公傳給曾孫,中間要經過爸爸、叔叔、伯伯...每個人都要經手一次,超煩。

為了解決這個問題,就出現了各種狀態管理的方案。官方有 Context API,社群有 Redux、Zustand、Jotai...等等一大堆。到底該怎麼選?

老實說,沒有標準答案。但我可以給你一個我自己判斷的思路,我把它整理成一個表格。

解決方案 適合情境 優點 缺點(或說...要注意的地方)
Prop Drilling (硬傳) 超簡單的 App,元件層級不超過 2-3 層。 最直覺,不用學新東西,程式碼流向很清楚。 元件一多、層級一深,馬上變義大利麵,改個東西要改一整串。
Context API 中小型 App,需要共享的狀態不多,例如:主題 (Theme)、使用者登入狀態。 官方內建,不用裝東西,API 也算簡單。 這是大坑:只要 Context 的 value 一變,所有用到這個 Context 的元件「全部」都會 re-render,不管它是不是真的需要那個變動的部分。很容易有效能問題。
Zustand / Jotai (原子化狀態) 從中型到大型 App 都很適合。我自己現在的新專案幾乎都用 Zustand。 寫起來像 Hook,學習曲線低。可以做到只更新「有訂閱」該狀態片段的元件,效能好很多。不用像 Redux 那樣寫一堆 boilerplate code。 畢竟是第三方庫,需要額外安裝。團隊成員需要一點時間習慣它的思維模式。
Redux (+ Redux Toolkit) 超大型、超複雜的企業級應用。狀態變更有嚴格的追蹤需求。 狀態流向超級嚴謹、可預測,搭配 Redux DevTools 根本是除錯神器。生態系非常成熟。 寫起來很囉嗦,概念也比較繞。為了改一個小狀態,要寫 action, reducer... 新手會覺得很挫折。現在有了 Redux Toolkit 已經好很多了,但還是比 Zustand 複雜。

所以你看,沒有哪個是最好的,只有最適合的。對於大多數專案,我會說從 Context 開始,如果發現效能瓶頸了,或是狀態變得太複雜,再考慮遷移到 Zustand。Redux...說真的,除非你的專案規模跟 Facebook 一樣大,不然可能有點殺雞用牛刀了。

還有一些雖然不常用,但關鍵時刻能救命的技巧

除了上面那些大主題,還有幾個小技巧,平常可能用不到,但一出事的時候,你會很感謝自己知道這些東西。

第一個是 Error Boundaries。你的 App 再怎麼測,上線後就是有可能會壞掉。最慘的情況是,某個小元件一壞,整個 App 白畫面給使用者看。Error Boundaries 就是一個「保險絲」,你可以用它包住你的元件,如果裡面的元件在 render 的時候出錯了,它會接住這個錯誤,然後顯示一個你預先準備好的「壞掉畫面」,而不是讓整個 App 崩潰。

不過要注意,根據 React 官方文件(這就是我說的非本地權威來源),它只能抓到 render 階段的錯誤。像是在 `useEffect` 裡的非同步錯誤、或是在 event handler(比如 `onClick`)裡的錯誤,它是抓不到的。那些你還是得用傳統的 `try...catch` 去處理。

第二個是 Portals。這東西超酷。一般來說,一個 Component 的 HTML 最終會被 render 到它在 DOM tree 裡的位置。但有時候,你希望把這個 Component 的 HTML「丟」到頁面的另一個地方去。最常見的例子就是 Modal (彈出視窗) 或 Tooltip (提示框)。你希望 Modal 的 HTML 結構是直接掛在 `body` 底下,這樣才不會被父層的 `z-index` 或 `overflow: hidden` 之類的 CSS 搞到,但它的邏輯跟狀態又明明是屬於某個子元件的。Portals 就是做這件事的橋樑。

最後一個,Server-Side Rendering (SSR)。這已經不只是 React 的範疇了,通常會搭配像 Next.js 這樣的框架來做。簡單說,傳統的 React App (CSR, Client-Side Rendering) 送到瀏覽器的是一個空的 HTML 跟一包很大的 JavaScript,瀏覽器要下載、執行完 JS 才能把畫面畫出來。這對 SEO 很不友善,對使用者來說也可能要看一段時間的白畫面。SSR 則是在 Server 端就先用 React 把頁面 render 成完整的 HTML 字串,再送給瀏覽器。這樣使用者可以馬上看到內容,搜尋引擎也看得很開心。這點在台灣的電商網站尤其重要,像是 PChome 或 momo,他們的首頁載入速度跟 SEO 排名是生死存亡的關鍵,所以他們很多頁面都會用上類似 SSR 或 SSG (Static Site Generation) 的技術,這跟國外很多純粹做後台系統的 SaaS 產品的架構思路就很不一樣。

Server-Side Rendering (SSR) 的概念:在伺服器端直接生成完整的 HTML 頁面。
Server-Side Rendering (SSR) 的概念:在伺服器端直接生成完整的 HTML 頁面。

那...我該從哪一個開始學?

如果看到這邊你覺得有點資訊爆炸,很正常。我自己的建議是,你不用一次全會。你可以根據你現在專案的痛點來決定優先級。

  • 網站載入慢? → 先去研究 React.lazy 和 Code Splitting。
  • 用起來卡卡的? → 打開 React DevTools 裡的 Profiler,看看是哪個元件在鬼打牆一直 re-render,然後試試看用 React.memo
  • 程式碼亂成一團,不敢改? → 這絕對是引入 Custom Hooks 的最佳時機。把你重複的邏輯抽出來。
  • Props 傳到天荒地老? → 該是時候導入狀態管理了。從 Context API 小試身手,或直接勇敢挑戰 Zustand。

說到底,這些工具都只是為了解決問題。重點是先定義出「問題」到底是什麼。這就是我一開始說的「判斷力」。一個資深開發者的價值,不是他會背誦多少 API,而是他能準確診斷問題,然後從他的工具箱裡,拿出最適合的那一把扳手。

好啦,今天大概就先聊到這。不知道我這樣東拉西扯的,對你有沒有幫助。如果你現在只能選一個技巧去深入研究三個月,你會選哪個?Code Splitting?還是狀態管理?在下面留言聊聊吧,我還蠻好奇大家現在都卡在哪個關卡。

Related to this topic:

Comments

  1. Guest 2025-08-03 Reply
    聽說這份技術指南超級實用!我的孩子正在學習前端開發,可以麻煩幫忙整理一下重點嗎?對於初學者來說,這些技巧聽起來有點複雜,但感覺很有學習價值。
  2. Guest 2025-07-29 Reply
    聽起來有點像是老生常談耶,這些技巧真的能解決實際開發痛點嗎?感覺有點像是紙上談兵,不過說不定我想法有點狹隘,還是想聽聽你的看法。
撥打專線 LINE免費通話