iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
佛心分享-IT 人職涯歷練

從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始系列 第 19 篇

Day 19|Filter 只是展開,為什麼 API 卻又打了一次?

  • 分享至 

  • xImage
  •  

前 18 天,我們一路從:

資料在哪裡?
↓
State / Props
↓
Component
↓
useEffect
↓
useRequest
↓
API
↓
Render
↓
key

慢慢把 React 和資料流接起來。

從今天開始,我想正式進入另一個階段:

不是再學一個新語法,而是拿這些觀念去 Debug 真實問題。

第一個案例,就是我曾經遇過的一個很典型的問題:

使用者只是展開 Filter,為什麼 API 又重新打了一次?


先看表面現象

假設頁面上有一個搜尋區塊:

搜尋條件
↓
展開更多 Filter
↓
顯示更多欄位

需求看起來很單純。

但實際操作時卻發現:

第一次進頁面
↓
API Request

點「展開」
↓
API Request 又來一次

點「收合」
↓
又 Request

第一個反應很容易是:

是不是 API function 被多呼叫了?

於是開始找:

request()
reload()
useEffect()
useRequest()

但後來真正的問題不一定在 Request 本身。

而是在:

Filter Component 被重新建立了。


React 裡的「重新 Render」和「重新建立」不是同一件事

這裡很容易混。

React Component 可以:

重新 Render

但仍然是同一個 Component instance。

也可能:

原本 Component 被移除
↓
unmount

重新出現
↓
mount

這就是完全不同的事情。

可以先簡單分:

Re-render
→ 同一個 Component 再算一次畫面

Remount
→ 舊 Component 消失
→ 建立一個新的 Component

這兩件事對 API Request 的影響可能差很多。


為什麼 remount 會讓 Request 又跑?

假設某個 Filter Component 裡有:

useRequest(fetchOptions);

或某種「Component 建立時自動載入資料」的邏輯。

第一次:

Filter mount
↓
Request
↓
取得 options

如果只是正常 re-render,

Component 沒有被摧毀,

不一定會重新走一次初始化。

但如果展開 / 收合的寫法造成:

Filter unmount
↓
Filter mount

那對 React 來說:

這是一個新的 Component。

所以:

初始化
↓
Request

又會再跑一次。


一個常見的寫法

例如:

{showFilter && (
  <FilterPanel />
)}

當:

showFilter = true

畫面:

FilterPanel mount

當:

showFilter = false

畫面:

FilterPanel unmount

再改回:

showFilter = true

又會:

建立新的 FilterPanel
↓
mount

所以如果裡面有初始化 Request,

它就可能重新發生。


畫面看起來只是「隱藏」,實際上可能是「刪掉」

這也是我後來才比較有感的地方。

對使用者來說:

收合 Filter

看起來只是:

不要顯示。

但程式寫法可能是:

showFilter
  ? <FilterPanel />
  : null

這代表:

不顯示
=
Component 根本不存在

所以:

展開
→ 建立

收合
→ 刪掉

再展開
→ 再建立

這就不是單純的 UI 顯示控制。


如果需求只是隱藏,其實不一定要 unmount

假設真正需求只是:

Filter 收起來時不要看到,但它的狀態和初始化不要消失。

那就可以考慮:

Component 保留
↓
只控制 CSS 顯示

例如概念上:

<div
  style={{
    display: showFilter
      ? "block"
      : "none",
  }}
>
  <FilterPanel />
</div>

這時候:

FilterPanel

一直都存在。

只是:

看得到 / 看不到

不同。

資料流就變成:

第一次 mount
↓
Request 一次

之後展開 / 收合
↓
Component 還在
↓
只改 display
↓
不需要重新初始化

這和「用 CSS 隱藏」不是偷懶

以前我可能會覺得:

都 React 了,怎麼還用 CSS hide?

但後來發現,真正要看的是需求。

如果需求是:

不需要保留 Component

那 unmount 很合理。

但如果需求是:

只是暫時不要顯示
↓
State 要保留
↓
不希望初始化重新發生

那保留 Component、只控制顯示,反而可能更符合需求。

所以不是:

CSS 比 React 差

而是:

你到底想「隱藏」,還是想「移除」?


我一開始其實想從 Request 下手

遇到重複 Request 時,很自然會想:

能不能加條件?
↓
不要重複打

例如:

加 cache
加 reqKey
加判斷
改 reload

這些方法可能真的能把 Request 擋掉。

但如果 Root Cause 是:

Component 一直被 remount,

那我們其實是在 Request 層幫 UI 結構擦屁股。

這樣做可能會讓問題越來越複雜。


Debug 時要分清楚「症狀」和「根因」

表面症狀:

API 重複 Request

真正原因可能是:

Filter 展開 / 收合
↓
Component unmount / mount
↓
初始化重新執行
↓
Request 重打

所以如果只盯著:

Request

很容易一直在錯的層修。

這也是我後來越來越常提醒自己的:

Bug 出現在哪裡,不代表 Root Cause 就在哪裡。


我現在會怎麼查這類問題?

第一步:

先重現

進頁面
↓
Request 一次

點展開
↓
Request 第二次

確認真的和:

showFilter

有關。


第二步:

找展開 / 收合控制的是什麼

例如:

{showFilter && <FilterPanel />}

第三步:

問:

showFilter 改變後
FilterPanel 是:

重新 Render?
還是 unmount → mount?

第四步:

再看 FilterPanel 裡:

誰會在 mount 時 Request?

例如:

useRequest
useEffect
Component 初始化

最後才得到完整流程:

showFilter 改變
↓
FilterPanel 被移除 / 建立
↓
初始化重新執行
↓
API Request

這樣 Root Cause 才完整。


為什麼我現在不會先改 Request?

因為 Request 本身可能完全正常。

例如:

Component mount
↓
Load options

這本來就是正確行為。

真正錯的是:

Component 不應該因為單純展開 / 收合就一直重新 mount。

所以比較合理的修正可能是:

保留 Component
↓
用 display 控制顯示

而不是:

Request 多加十個條件

這其實就是前面 18 天的觀念開始一起工作

如果只知道:

API 重複打

很容易只查 API。

但前面我們已經學過:

Component
State
Render
useRequest
Effect
key

所以現在可以開始畫:

showFilter State
↓
Render
↓
Component 是否存在?
↓
mount / unmount
↓
Request lifecycle

這就是我覺得從「會語法」走向「會 Debug」很重要的一步。


今天真正想記的是

遇到:

某個操作
↓
API 突然又 Request

不要第一個就問:

「API 怎麼不要重打?」

先問:

這個操作有沒有讓 Component 被重新建立?

State 有沒有被重置?

Effect / Request 初始化是不是又跑了一次?

也就是:

先找是誰觸發 Request,再決定該在哪一層修。

最後這個案例的重點對我來說不是:

CSS display 比 conditional rendering 好

而是:

真正的解法要對準 Root Cause。

有時候 API 重複 Request,

真正要修的並不是 API。

下一篇可以接另一個很類似的 Debug 思考:

明明有 reload(),為什麼資料就是不重新抓?Cache、Request Key 到底是在幫忙還是在搗亂?


上一篇
Day 18|key 到底是幹嘛的?為什麼不能每次都直接用 index?
下一篇
Day 20|明明有 reload(),為什麼資料就是不重新抓?Cache、Request Key 到底在幫忙還是在搗亂?
系列文
從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始 共 20 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言