React中如何渲染属性动态变化的数据点列表并避免不必要重渲染
问题根因
你当前的代码触发全量重渲染的原因有两个:
- 父组件状态更新时,所有子组件默认会跟随重渲染,未做渲染拦截
updateDataValue函数每次父组件渲染都会生成新的引用,导致所有子组件的props都发生变化,必然触发重渲染
方案1:拦截不必要重渲染(推荐,符合React单向数据流设计)
不需要用useMemo,用React.memo + useCallback组合即可实现,修改点如下:
- 用
React.memo包裹DataPoint组件,只有props发生浅比较变化时才会触发重渲染 - 用
useCallback包裹updateDataValue函数,同时状态更新改用函数式写法,固定函数引用,避免每次父组件渲染生成新的函数 - 最终只有值发生变化的单个数据点会触发重渲染
修改后的完整代码:
import React from 'react' import _ from 'lodash' const numDataPoints = 3 const dataPointsIndices = _.range(0, numDataPoints).map(id => id.toString()) const initDataPointValues = dataPointsIndices.reduce( (acc, id) => ({ ...acc, [id]: '' }), {} ) const DataPoints = () => { const [dataPointValues, setDataPointValues] = React.useState(initDataPointValues) // 用useCallback固定函数引用,函数式更新避免依赖dataPointValues const updateDataValue = React.useCallback((id, { value }) => { setDataPointValues(prev => ({ ...prev, [id]: value, })) }, []) return ( <div> <button onClick={() => console.log(dataPointValues)}> Log all values </button> <div> {dataPointsIndices.map(id => ( <DataPoint key={id} id={id} value={dataPointValues[id]} updateDataValue={updateDataValue} /> ))} </div> </div> ) } // 用React.memo做渲染拦截 const DataPoint = React.memo(({ id, value, updateDataValue }) => { console.log('RENDER DATA POINT:', id, value) return ( <div> {`data point: ${id}`} <textarea value={value} onChange={event => { const newValue = event.target.value updateDataValue(id, { value: newValue }) }} /> </div> ) }) export default DataPoints
方案2:子组件存储本地状态
如果要采用子组件本地存状态的方案,可通过ref收集子组件的值,实现步骤:
- 父组件创建ref数组存储所有子组件的实例引用
- 子组件通过
useImperativeHandle暴露获取当前值的方法 - 需要聚合全量值时,遍历所有ref调用获取值的方法即可
该方案适合数据量极大、不想在父组件维护全量状态的场景,但不如方案1易维护。
内容的提问来源于stack exchange,提问作者desa
相关产品推荐
相关产品推荐

