详解React如何优雅地根据prop更新state值
作者:晓得迷路了
快速总结:
- 在渲染函数中直接根据 prop 计算状态或者通过 key 值重新渲染整个组件 。
- 记录之前状态的 prop 值,直接在组件渲染函数中与当前 prop 值对比,有变化就更新 state 值。
- 使用 useEffect 或者 useLayoutEffect。
场景
开发 React 组件中,有时会碰到同步状态的问题,就是当 prop 变化时调整组件的 state 值。
比如以下这个场景:一个下拉组件 Select 内置了两组选项内容,支持通过 type
来选择,同时也支持传入 options
参数自定义选项内容。
代码实现方面,内部维护一个 innerOptions
,外部参数 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> ); }
正常使用这个组件时一切都是正常的。
问题出现
不过当你需要在一个 Select 组件中切换 type
时,就会出现一个问题,切换过程中间会闪现过一个 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
中更新的,也就是中间会出现一次 value
为新值,而 innerOptions
为旧值的渲染。
寻找解决方案
既然找到了原因,就开始想办法处理这个问题。
useLayoutEffect
第一时间想到了用 useLayoutEffect 能否解决, useLayoutEffect 在 react 文档中说明是在将内容真正渲染到屏幕前调用,并且会阻塞浏览器重绘。
将 useEffect 全部改成 useLayoutEffect。
useLayoutEffect(() => { if (type !== undefined) { setInnerOptions(optionsList[type]) } }, [type]); useLayoutEffect(() => { if (options !== undefined) { setInnerOptions(options) } }, [options])
可以看到不会出现 undefined
的情况了。
useLayoutEffect 会降低性能。在可能的情况下,最好使用 useEffect。
不过官方文档有提到 useLayoutEffect 会降低性能,再看看是否有更好的方案。
react 最佳实践
prop 改变后 state 同步这个在 react 文档中有编码建议。
官方推荐不要使用 useEffect,在函数中直接调整 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 的代码可能并不常见。
当你在组件渲染函数中直接更新组件时,React 会丢弃返回的 JSX 并立即重新渲染。不过为了避免非常缓慢的级联重试,React 只允许你在组件函数中更新同一组件的状态。
也就是说 value
为新值,而 innerOptions
为旧值的渲染被丢弃了,所以不会出现 undefined
的情况,问题也得到了解决。
总结
正常情况下,我们使用 useEffect 来将 prop 更新到 state 是没问题的。不过在有界面渲染的情况下,可能会有 bug 出现,这时需要使用 useLayoutEffect 或者直接在组件渲染函数中更新 state 值。
其实根据 prop 更新 state 在非必要的情况下尽量不要出现,优先考虑在渲染函数中直接根据 prop 计算状态或者通过 key 值重新渲染整个组件
例如以下方式处理 innerOptions:
// 直接根据 prop 计算状态 const innerOptions = optionsList[type] || options || []
以上就是详解React如何优雅地根据prop更新state值的详细内容,更多关于React更新state值的资料请关注脚本之家其它相关文章!