前言
近年前端技術層出不窮,Facebook 推出的 React 也是程序員間討論最多的 Javascript 框架之一。借這個機會開始學習 React,並把筆記作為 React 基礎教程系列(參考 React 官網)。
因為屬於入門,不太適合對 React 有經驗的同學,但希望到此的同學多少都有所收穫。
為了更好的閱讀體驗,可以在文章底下的“瞭解更多”中查看原文。
派生 state
我語文可能是體育老師教的,對於派生這個詞語不知道是什麼意思,先解釋下什麼是"派生":
派生(derived):本指江河的源頭產生出支流。引申為從一個主要事物的發展中分化出來。
那麼放在 Javascript 中,對於"派生 state"就好理解了,無非就是 state 狀態對象的值由其他變量賦值得到,而不是我們對 state 進行初始化創建的。
那麼就對於這樣的 state ,在如下兩個場景就會有"副作用",使得程序出現非預期的 bug 問題。
避免將 props 的值複製給 state
場景說明:
父組件 EmailReset 中有個定時器,頁面渲染後將觸發定時器,不斷刷新 state 狀態對象;
子組件 EmailInput 有個 Input 輸入框,接受 props.email 屬性,並將其作為 Input 輸入框的 value 顯示。
代碼示例:
頁面 HTML 展示:
父組件 EmailReset:
注意:
在 componentDidMount 生命週期中,定義並執行定時器 timer;
並對子組件 EmailInput 設置屬性 email(此 email 和 state 並沒有關係,此處還是個"死值")。
子組件 EmailInput:
一個典型 React 受控組件例子,Input 輸出後觸發事件方法,更新 state 對象重新渲染後回顯到頁面上。
問題:
看似沒有問題,但當用戶對 Input 輸入數據後,會被初始化成原始值。
頁面 HTML 效果:
結合瀏覽器控制檯調試信息,和頁面 HTML Input 輸入框所示,我們看到三種現象:
1. 每次父組件定時器執行,更新 state 對象,觸發 render 方法
2. 之後,每次還會觸發子組件的生命週期方法:static getDerivedStateFromProps 和 componentWillReceiveProps(現在不建議使用)
3. 頁面 HTML 的 Input 輸入框值被還原
那怎麼解決?
代碼示例:
嘗試更改邏輯設計,增加如上判斷。這樣每次父組件 state 狀態的更新,雖然還會觸發子組件的生命週期 componentWillReceiveProps 方法,但由於這樣的判斷,就不會重置 email 數據狀態。
好像展示解決的這個 bug,但稍有疏忽還會引發其他問題。
在 props 變化後修改 state
場景說明:
針對上面那個解決方案,再引申舉個反例。
比如,一個頁面 HTML 中有多個 tab 標籤,每個 tab 對應不同的 Input 輸入框初始數據。
代碼示例:
父組件 EmailFormTab:
定義父組件的 tab 欄,以及點擊事件 onChooseTab:
邏輯這樣:點擊不同 tab,觸發對應事件,獲取點擊 tab 索引,更新對應 state 狀態;從而每次渲染後,currentEmail 為 emailList 對應的 email 值。
子組件同上。
問題:
看似也沒什麼問題,但注意前兩個 tab 對應的 email 數據相同時,每當在 tab1 下,對 input 輸入新值,再點擊 tab2 ,會使得 input 變為前面輸入的值。
這肯定不是我們設計上的原意(雖然公用一個 input,邏輯上每次切換 tab 時,希望他們保持獨立)。
頁面 HTML 效果:
原因就是 static getDerivedStateFromProps or componentWillReceiveProp 根本就是有問題的。
注意,props.email (每次點擊 tab 時,傳入的值) 和 this.props.email (初始化時的值)的判斷永遠進入不了,導致 state 一直更改不了。
input 的 value 一直沿用之前 currentEmail.email (tab1 輸入時的值)。
解決方案
使用"完全"可控的組件
由於子組件 EmailInput 對於 Input 上事件觸發,都是通過 state 狀態自身管理。所以需要將 onChange 移出子組件,以及 value 對應的 state 取值,都放到父組件中控制,再通過數據屬性 props 傳遞給子組件。
這樣子組件就是個完全可控的組件了。
子組件,將 state 替換為 props:
父組件:
重新定義初始化的 state 狀態對象:
當父組件觸發響應方法後,更新 state :
通過屬性定義,傳入子組件:
頁面 HTML 效果:
使用有 key 描述的可控組件
把代碼恢復到之前的樣子,只添加 key 屬性,讓每次渲染子組件時,都根據 key 新創建獨立的子組件。
代碼示例:
總結
根據兩個不恰當的例子說明,我們基本瞭解對派生 state 的使用要及其注意。
對 static getDerivedStateFromProps 和 componentWillReceiveProps 兩個生命週期中添加的邏輯要慎重!因為說不定會像上例中出現"重置的效果",也不要試圖用自認為沒問題的邏輯判斷解決派生 state 相關問題。
另外區分好受控和非受控組件的含義。有必要時,不要怕麻煩,在父組件中定義好事件函數傳給子組件;
也可以定義 key 屬性,或者人為邏輯中增加獨一的 ID 設計以區分。