Reset State When State Chagne
我現在正在做一個 Popup 元件(component),內容是一個表單,以下是幾個需求
- 點擊外面、叉叉和 Cancel 要關掉
- 點擊 Save 要做一些事後關掉
- 無論怎麼關掉的,都要清除表單狀態(reset)
- 這個頁面會有不同 Popup,裡面的表單不一樣
Popup 元件設計
|
|
像這樣的設計我就可以把 Popup 和表單抽離,例如這樣
|
|
我知所以把 MyForm
抽出來做成另一個元件,是因為我覺得我的 Page
函數太長了,想分開
問題
這樣看起來很棒,設計得很有彈性,也達成了大部分需求,但是需求 3 卻沒有達成,問題就在我是用 display: none
隱藏 Popup
的,因此下次再打開 Popup
時對於 React 來說這個元件沒變化,不會重設一個新的 Popup
。
我如何解決的
我曾經想過,手動在 MyForm
兩個按鈕的事件加上 updateDate(defaultValue)
,但是這樣的話,點擊表單外面半透明的區域關掉表單不會清除表單內容,如果把表單和 Popup 合在一起又破壞了當初彈性的設計。不合起來的話也是做得到,但是會變得很複雜不好維護。
最後,我只在 Popup 上新增了一個 key
屬性,就解決了這個問題。
元件的生命週期
要解釋這個問題就要先解釋元件的生命週期和 React 如何處理 state。一個元件的 state 在整個元件的生命週期中,React 會保證這個 state 是同一個 value,直到元件不再出現在 virtual DOM 上。
React 會把每個「frame」在 virtual DOM 中相同位置的同類型元件視為相同(例如 Popup 和 Popup、button 和 button),並且把上一個 frame 的 state 塞給這個元件,當 React 發現原本這裡有個元件,但是現在卻找不到了,那麼 React 就會視這個元件「消亡」,隨之清除原本應該給這個元件的 state。
key
除了檢查位置以外,React 還會檢查這個元件的 key
屬性,即使位置一樣,key
不一樣 React 依舊會把他視為是一個新的元件,並給他全新的 state。
這個特性最常出現在用一個陣列去批次產生 JSX 的時候
|
|
這樣當插入、刪除 userIds
其中某個元素時,即便位置不一樣 React 還是可以透過 key
去追蹤元件,並給他正確的 state
利用 key 去清除 state
那麼,我們需要的就是在 open
有變化時,塞給 Popup
一個和上次不同的 key,就可以讓 React 知道這是一個新的元件,要給他新的 state
|
|
這裡用 Number()
包起來只是因為 typescript 會抱怨 boolean 不能當作 key。如此一來只改一行,就可以達成「關掉 Popup 時清空表單」,不管是怎麼關掉的。