詳解React如何優(yōu)雅地根據(jù)prop更新state值
快速總結:
- 在渲染函數(shù)中直接根據(jù) prop 計算狀態(tài)或者通過 key 值重新渲染整個組件 。
- 記錄之前狀態(tài)的 prop 值,直接在組件渲染函數(shù)中與當前 prop 值對比,有變化就更新 state 值。
- 使用 useEffect 或者 useLayoutEffect。
場景
開發(fā) React 組件中,有時會碰到同步狀態(tài)的問題,就是當 prop 變化時調(diào)整組件的 state 值。
比如以下這個場景:一個下拉組件 Select 內(nèi)置了兩組選項內(nèi)容,支持通過 type
來選擇,同時也支持傳入 options
參數(shù)自定義選項內(nèi)容。
代碼實現(xiàn)方面,內(nèi)部維護一個 innerOptions
,外部參數(shù) type
或 options
通過 useEffect
來同步,相關代碼如下:
import React, { useState, useEffect } from "react"; import { Flex, Space, Select } from "antd"; function MySelect({ value, onChange, type, options }) { const optionsList = [[ { value: "dog", label: "小狗" }, { value: "cat", label: "小貓" }, ], [ { value: "banana", label: "香蕉" }, { value: "apple", label: "蘋果" }, ]] const [innerOptions, setInnerOptions] = useState(options || optionsList[type] || []) useEffect(() => { if (type !== undefined) { setInnerOptions(optionsList[type]) } }, [type]); useEffect(() => { if (options !== undefined) { setInnerOptions(options) } }, [options]) return ( <Space direction="vertical"> {`你選擇了:${innerOptions.find((item) => item.value === value)?.label}`} <Select style={{ width: 120 }} value={value} onChange={onChange} options={innerOptions} /> </Space> ); }
正常使用這個組件時一切都是正常的。
問題出現(xiàn)
不過當你需要在一個 Select 組件中切換 type
時,就會出現(xiàn)一個問題,切換過程中間會閃現(xiàn)過一個 undefined
。
import React, { useState } from "react; import { Flex, Space } from "antd"; import MySelect from "./MySelect"; function App() { const [value, setValue] = useState("dog"); const [type, setType] = useState(0); const handleChange = (newVal) => { setValue(newVal); }; const handleChangeType = (e) => { setType(e.target.value); setValue(e.target.value === 0 ? 'dog' : 'banana') } return ( <div className="App"> <Flex gap="middle" vertical> <Radio.Group onChange={handleChangeType} value={type}> <Radio value={0}>動物</Radio> <Radio value={1}>水果</Radio> </Radio.Group> <MySelect type={type} value={value} onChange={handleChange} /> </Flex> </div> ); }
問題的原因是切換選項時,value
值是直接更新的,而 innerOptions
是 useEffect
中更新的,也就是中間會出現(xiàn)一次 value
為新值,而 innerOptions
為舊值的渲染。
尋找解決方案
既然找到了原因,就開始想辦法處理這個問題。
useLayoutEffect
第一時間想到了用 useLayoutEffect 能否解決, useLayoutEffect 在 react 文檔中說明是在將內(nèi)容真正渲染到屏幕前調(diào)用,并且會阻塞瀏覽器重繪。
將 useEffect 全部改成 useLayoutEffect。
useLayoutEffect(() => { if (type !== undefined) { setInnerOptions(optionsList[type]) } }, [type]); useLayoutEffect(() => { if (options !== undefined) { setInnerOptions(options) } }, [options])
可以看到不會出現(xiàn) undefined
的情況了。
useLayoutEffect 會降低性能。在可能的情況下,最好使用 useEffect。
不過官方文檔有提到 useLayoutEffect 會降低性能,再看看是否有更好的方案。
react 最佳實踐
prop 改變后 state 同步這個在 react 文檔中有編碼建議。
官方推薦不要使用 useEffect,在函數(shù)中直接調(diào)整 state 值。
const [innerOptions, setInnerOptions] = useState(options || optionsList[type] || []) const [prevType, setPrevType] = useState(type); const [prevOptions, setPrevOptions] = useState(options); if (prevType !== type) { setPrevType(type); setInnerOptions(optionsList[type]) } if (prevOptions !== options) { setPrevOptions(type); setInnerOptions(options) }
上面的代碼比起使用 useEffect 的代碼可能并不常見。
當你在組件渲染函數(shù)中直接更新組件時,React 會丟棄返回的 JSX 并立即重新渲染。不過為了避免非常緩慢的級聯(lián)重試,React 只允許你在組件函數(shù)中更新同一組件的狀態(tài)。
也就是說 value
為新值,而 innerOptions
為舊值的渲染被丟棄了,所以不會出現(xiàn) undefined
的情況,問題也得到了解決。
總結
正常情況下,我們使用 useEffect 來將 prop 更新到 state 是沒問題的。不過在有界面渲染的情況下,可能會有 bug 出現(xiàn),這時需要使用 useLayoutEffect 或者直接在組件渲染函數(shù)中更新 state 值。
其實根據(jù) prop 更新 state 在非必要的情況下盡量不要出現(xiàn),優(yōu)先考慮在渲染函數(shù)中直接根據(jù) prop 計算狀態(tài)或者通過 key 值重新渲染整個組件
例如以下方式處理 innerOptions:
// 直接根據(jù) prop 計算狀態(tài) const innerOptions = optionsList[type] || options || []
以上就是詳解React如何優(yōu)雅地根據(jù)prop更新state值的詳細內(nèi)容,更多關于React更新state值的資料請關注腳本之家其它相關文章!
相關文章
react 不用插件實現(xiàn)數(shù)字滾動的效果示例
這篇文章主要介紹了react 不用插件實現(xiàn)數(shù)字滾動的效果示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-04-04create-react-app使用antd按需加載的樣式無效問題的解決
這篇文章主要介紹了create-react-app使用antd按需加載的樣式無效問題的解決,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-02-02詳解Webpack+Babel+React開發(fā)環(huán)境的搭建的方法步驟
本篇文章主要介紹了詳解Webpack+Babel+React開發(fā)環(huán)境的搭建的方法步驟,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-01-01