如何避免使用useState的React函数式组件出现重复渲染问题
问题根因
- useEffect依赖缺失:在监听selectAll的useEffect中使用了
steps变量,但未将其加入依赖数组,同时未判断当前选中状态是否与目标状态一致,反复调用setSelected触发无限重渲染 - React.memo未生效:默认仅对props做浅比较,父组件传递的
onSelected等回调函数如果未用useCallback包裹,每次父组件渲染都会生成新的函数引用,导致memo跳过重渲染的逻辑失效 - 变量拼写错误:props解构出的变量是
Id,但useQuery中使用的是小写id,且use变量未在代码中定义,会引发额外的渲染异常
修复方案
- 补全useEffect依赖,增加状态相等性判断,避免无意义的状态更新
- 为React.memo自定义比较逻辑,或要求父组件用
useCallback包裹传递的回调函数 - 修正变量拼写错误,移除临时兼容代码
- 补全
isSelected、handleClick的逻辑,确保选中状态计算正确
修正后代码
import { isEqual } from 'lodash'; // 也可以自己实现数组相等判断逻辑 function OptimiserTableBody(props) { const { id, columns, send, onSelected, selectAll } = props; // 修正变量名大小写 const [selected, setSelected] = useState([]); // Retrieve the data const resOData = useQuery(o_data_query, { variables: { id: id, use: use // 注意这里的use变量需要从props或其他合法来源引入 }, fetchPolicy: 'cache-and-network' }); // 用useMemo缓存steps,避免每次渲染生成新数组 const steps = useMemo(() => { return resOData.data?.jobO?.data || []; }, [resOData.data]); useEffect(() => { if (selectAll === true) { const newSelects = steps.map((n) => n.workStepId); // 状态一致时不触发更新,避免重渲染 if (!isEqual(selected, newSelects)) { setSelected(newSelects); } } }, [selectAll, steps, selected]); // 补全所有用到的依赖 // 补全isSelected逻辑 const isSelected = (workStepId) => { return selected.includes(workStepId); }; // 用useCallback包裹点击回调,避免每次渲染生成新函数 const handleClick = useCallback((event, workStepId) => { const newSelected = selected.includes(workStepId) ? selected.filter(id => id !== workStepId) : [...selected, workStepId]; setSelected(newSelected); onSelected?.(newSelected); // 调用父组件回调 }, [selected, onSelected]); return ( <TableBody> {steps.map((step, index) => { const isItemSelected = isSelected(step.workStepId); return ( <TableRow key={step.workStepId}> <TableCell key={'select'} padding="checkbox"> <Checkbox checked={isItemSelected} inputProps={{ 'aria-labelledby': step.workStepId }} style={{color: kbaColors.grey1}} onClick={(event) => handleClick(event, step.workStepId)} /> </TableCell> </TableRow> ) })} </TableBody> ); } // 自定义memo的比较逻辑,按需控制重渲染条件 export default React.memo(OptimiserTableBody, (prevProps, nextProps) => { return ( prevProps.id === nextProps.id && prevProps.selectAll === nextProps.selectAll && isEqual(prevProps.columns, nextProps.columns) // 其他需要比较的props按需添加 ) });
额外优化建议
- 父组件传递
onSelected、send等回调函数时,用useCallback包裹,固定函数引用,进一步降低不必要的重渲染 - 如果表格数据量很大,可以把单独的
TableRow也封装为memo组件,避免单行数据变化时整个表格重渲染
内容的提问来源于stack exchange,提问作者Neal Rogers
相关产品推荐
相关产品推荐

