為什麼 React 在「沒資料時」也要先渲染?——揭開 useEffect 與元件渲染時機的真相

更新日期: 2025 年 4 月 18 日

你是否在學習 React 時,有過這樣的困惑?

  • 我明明要等 API 回傳資料再顯示,為什麼 React 一開始就先 render 一次
  • 明明沒資料,畫面先空著不奇怪嗎?
  • useEffect 執行時不是應該是 componentDidMount 階段嗎?那不就是 render 完了才抓資料

別急,這篇文章就帶你完整釐清 React 的渲染邏輯與生命週期,理解為什麼這樣的設計,反而讓 React 更強大!


React 的核心哲學:UI = f(state)

React 的設計理念可以用一個數學公式來概括:

UI = f(state)

也就是說:使用者介面(UI)是狀態(state)的函數。

這代表什麼呢?

在 React 裡,你不需要手動告訴電腦何時該更新畫面,你只要做兩件事:

  1. 定義 state 是什麼
  2. 描述當 state 改變時,畫面要怎麼「自動」改變

React 會根據你宣告的狀態(如:空資料、有資料、錯誤等等),自動重新 render 出符合該狀態的畫面,這就是 宣告式程式設計(Declarative Programming)

傳統的命令式程式設計(Imperative Programming)

傳統的命令式寫法,你可能會這樣寫邏輯:

if (資料還沒來) {
  顯示 loading 畫面
} else if (有錯誤) {
  顯示錯誤畫面
} else {
  把資料放進畫面上
}

你得自己去判斷「何時該顯示什麼」、「畫面哪裡該改」,很容易漏掉某個狀態,或造成程式難以維護。

而在 React 裡,你只需要說明「當 state 是這樣時,畫面長這樣」,不需要自己控制畫面更新邏輯,因為 React 幫你處理了所有畫面更新的時機點

為什麼資料還沒來,也要「先 render」?

我們來看一段實際例子:

function MemberList() {
  const [members, setMembers] = useState([]);

  useEffect(() => {
    fetch('/api/members')
      .then(res => res.json())
      .then(data => setMembers(data));
  }, []);

  return (
    <ul>
      {members.map(member => (
        <li key={member.id}>{member.name}</li>
      ))}
    </ul>
  );
}

🪜 React 背後的實際執行流程是這樣的:

步驟發生的事狀態值
1️⃣ 初次 rendermembers 是空陣列 → React 渲染出空的 <ul>[]
2️⃣ 掛載完成useEffect 執行,開始發送 API 請求[]
3️⃣ 資料回來呼叫 setMembers(data),更新狀態[member1, member2, ...]
4️⃣ 更新 renderReact 再次渲染畫面,顯示資料列表[member1, member2, ...]

你可能會問:

❓ 為什麼不「等資料回來後」再 render?

因為 React 設計上就是「每一種狀態都會被渲染出對應的畫面」,即使現在狀態是「還沒資料」,也能對應一個適當的畫面(例如:顯示 loading 中)。

這不但讓程式更模組化,也讓使用者體驗更流暢!


為什麼「沒資料也要渲染」是必要的?

提供立即回饋,改善使用者體驗

想像一下使用者點開頁面,畫面一片空白,會覺得程式壞了嗎?
這時你可以這樣寫:

if (members.length === 0) return <p>資料載入中...</p>;

讓使用者知道「資料正在載入中」,這是一種良好的 UX 實踐。

每一種 state 都有「對應的畫面」

你可以這樣思考:

狀態對應畫面
data = null顯示載入中
data.length === 0顯示「查無資料」
data.length > 0顯示資料列表

這種「狀態驅動 UI」的設計,讓你的元件更容易測試、更容易維護,也更接近使用者實際看到的畫面行為。

支援元件初始化即 render 的邏輯(懶加載、動畫、錯誤處理等)

很多 UI 行為其實在資料還沒到之前就要執行,例如:

  • 懶加載的動畫骨架(skeleton)
  • 圖表元件初始化動畫
  • 空資料時的提示畫面
  • 錯誤重試按鈕…

這些 UI 元素都需要「沒資料」的狀態就能 render,而不是等資料到才 render。

小結

React 是「狀態驅動的 UI 框架」,資料是否準備好,本身就是一種 state。
不管資料有沒有回來,React 都能依照目前的 state 先渲染對應的畫面,這才是它的強大之處。

所以下次你看到「還沒資料也先 render」,別懷疑——這不是 bug,而是 React 的特色與設計哲學。


元件渲染時機 vs. useEffect 執行時機

當你開始在 React 中撰寫 API 請求、動畫效果或訂閱行為時,常常會接觸到 useEffect

但它什麼時候執行?會不會影響畫面?與元件的渲染順序有什麼關係?這一段我們一次搞懂!

掛載階段(Mounting Phase):初次 render 的兩個階段

當元件第一次出現在畫面上,React 會經歷兩個步驟:

初次 render

React 根據當前的 state(例如空陣列、null)去建立 Virtual DOM → 計算出一個對應的 UI。

⚠️ 此時 useEffect 還沒執行,畫面已經先出現在瀏覽器上

完成掛載(commit phase)

當畫面真正顯示出來、DOM 結構已經渲染完畢,React 才會去觸發 useEffect(() => {...}, []) 中的副作用。

這通常包含:

  • 資料請求
  • DOM 操作(focus、scroll)
  • 設定訂閱(websocket、事件監聽)

✅ 所以:useEffect 的執行時機是在 render 之後、畫面顯示完成之後

這個流程非常像 Class Component 裡的 componentDidMount(),所以我們常說 useEffect(..., []) 模擬的就是「元件掛載完成時」的副作用。

更新階段(Updating Phase):當 state 改變時

當你呼叫 setState()setMembers(data) 改變狀態時,會進入 更新階段

  1. React 偵測到 state 或 props 改變
  2. 執行新的 render → 建立新的 Virtual DOM
  3. 對比新舊 DOM(diffing)
  4. 更新畫面中真正變動的部分(高效!)

✅ 這種重新 render 的行為是 React 的精髓

  • 你不用自己操作 DOM
  • 你只需要描述「畫面要長什麼樣」
  • React 自動幫你處理更新與重繪

這讓 UI 與資料狀態保持一致,也讓開發更有信心。

如果我不想讓畫面「先閃過空資料再更新」怎麼辦?

React 預設的行為是「先 render 初始狀態 → 資料載入後再更新畫面」,但有時候你不希望畫面這樣閃來閃去。

這時,你可以使用一個額外的 loading 狀態,搭配條件渲染。

const [loading, setLoading] = useState(true);
const [members, setMembers] = useState([]);

useEffect(() => {
  fetch("/api/members")
    .then(res => res.json())
    .then(data => {
      setMembers(data);
      setLoading(false); // 資料拿到了,關掉 loading 狀態
    });
}, []);

if (loading) return <p>資料載入中...</p>; // 或顯示 skeleton 動畫

return (
  <ul>
    {members.map(member => (
      <li key={member.id}>{member.name}</li>
    ))}
  </ul>
);

這樣可以避免「先空畫面 → 突然出現資料」的突兀感,改善使用者體驗。

常見疑問總整理

問題解答
為什麼 useEffect 是 render 完才執行?因為它模擬 componentDidMount,只有在 DOM 完成後才能執行副作用邏輯(例如操作 input、抓資料)
為什麼沒資料也要先 render?因為「沒資料」本身也是一種 state,React 會根據當前狀態先渲染對應畫面(例如 loading)
這樣 render 兩次,不會很耗效能嗎?不會。React 使用 Virtual DOM 做差異比對(diffing),只會更新有變化的部分,效能處理得非常好
如何避免畫面閃爍?加上 loading 狀態 + 條件渲染,讓畫面在資料回來前只顯示載入提示或 skeleton

小結

沒資料也 render?不奇怪,這正是 React 的設計之美!

React 要你專注在「描述畫面長怎樣」,而不是「怎麼控制畫面更新」。這樣能讓程式碼更直觀、邏輯更清楚、可維護性更高。

Similar Posts