我忘了我記得,所以再做一次
昨天建立天氣看板,加入城市與溫度單位切換,接著來看背後的概念:依賴如何決定重新同步,以及同一城市重新查詢的原理。
天氣查詢會使用 cityId 與 temperatureUnit,依賴的資料分別是 [cityId, temperatureUnit]。
React 會用 Object.is 比較每項依賴,當城市或單位改變時,會先清理上一輪再用新的設定開始查詢。
依賴反映 Effect 實際讀取且會隨渲染改變的資料,例如查詢使用溫度單位,卻只在依賴中列出城市,當單位改變時就不會重新同步。
| Effect 使用的內容 | 依賴 |
|---|---|
cityId、temperatureUnit |
來自 props,會隨渲染改變,需要列入 |
setWeather |
state setter 身分穩定,可省略 |
| 匯入的資料工具函式 | 宣告在元件外,不隨元件渲染重新建立 |
| setup 內建立的控制器 | 屬於這一輪工作,不是從渲染讀入的依賴 |
行程備註 note |
Effect 沒有讀取,不用列入 |
依賴要注意物件與函式的身分,雖然內容相同,但每次渲染新建的物件是不同參照,放進依賴就可能讓 Effect 頻繁重新同步。
元件重新渲染是依照目前資料更新畫面。Effect 重新同步則是在城市或單位改變後,清理上一輪查詢,再用新的設定查詢天氣。
| 情境 | 畫面的更新 | 天氣查詢 |
|---|---|---|
| 備註改變 | 顯示新備註 | 城市與單位沒變,維持原本查詢 |
| 收到天氣資料 | 顯示查詢結果 | 依賴沒變,不用再查一次 |
| 溫度單位改變 | 顯示新選項 | 清理舊工作,再查新單位 |
WeatherPanel 可以一直留在相同位置,同時經歷多輪查詢,cleanup 會在依賴改變時執行,也會在元件移除時執行。
如果重新設定相同的 cityId,但沒有提供新依賴,不會讓這份 Effect 重查。因此若需求是同一城市再查一次,可以用 state 保存一份重查版本:
//示意片段:
const [reloadVersion, setReloadVersion] = useState(0);
function handleReload() {
setReloadVersion((version) => version + 1);
}
按鈕事件增加版本,Effect 的依賴則變成 [cityId, temperatureUnit, reloadVersion],當版本從 0 變成 1,即使城市與單位相同也需要重新同步。
動作就會是:
要求重新查詢 → 版本增加 → 清理上一輪 → 開始新查詢
因為版本是本機的查詢控制資料,即便 API 是使用一樣的城市與單位,同一個網址也可以重新發送請求。
若結果身分 requestKey 與 currentKey 也包含版本,就能區分重查前後的資料,在等待新結果時先顯示 loading。
請求完成順序不保證等於開始順序,像是台北先查、高雄後查,但台北的舊回應可能晚到,所以每輪都要交代如何停止工作與處理結果。
| 寫法 | 用途 |
|---|---|
cleanup 中的 controller.abort() |
取消這一輪尚未完成的請求 |
成功與 catch 中的 controller.signal.aborted 判斷 |
已取消就略過後續結果更新 |
requestKey 與 currentKey 比對 |
呈現符合目前設定與版本的資料 |
身分比對只能決定顯示內容,不能停止請求,每輪的清理與取消判斷,還是要負責避免舊工作寫回結果。
城市名稱、備註摘要與時間文字都能從現有資料算出,不需要另外保存 state 再用 Effect 同步,這樣也能避免同時維護兩個相關的資料。
以天氣看板為例,事件負責增加重查版本,渲染負責計算畫面,Effect 負責查詢外部 API。
接續把查詢邏輯抽成 useWeather,讓不同城市卡片共用邏輯、各自保存狀態,明天再來看看吧。