React表格组件持续重渲染:如何修复元素频繁刷新问题?
修复React表格持续重渲染及高亮失效问题
核心问题原因
你的表格行(<tr>)和单元格(<td>)持续重渲染、高亮无法保留的根本原因是在map循环中使用uuidv4()生成动态key。React的Diff算法依赖key识别DOM元素身份,每次组件渲染时uuidv4()都会生成新随机值,React会判定这些是全新元素,销毁旧DOM节点并重新创建,导致高亮状态丢失,同时造成不必要的性能损耗。
具体修复步骤
替换动态
key为数据的唯一标识
找到ratings数据中每个item的稳定唯一属性(比如item.name,如果棋类变体名称唯一;或者数据自带的id字段),将key设置为这个固定值,确保同一行的key在每次渲染时保持不变:<tr key={item.name}>用
useCallback缓存排序函数
当前的vCycle、dCycle、rCycle函数会在组件每次渲染时重新创建,可能触发表头不必要重渲染。用useCallback包裹这些函数,保证函数引用稳定:import { React, useState, useEffect, useMemo, useCallback } from 'react'; // ... 其他代码 const vCycle = useCallback((currentVState) => { if (currentVState === 'default') { setVState('dsc'); setDState('default'); setRState('default'); } else if (currentVState === 'dsc') { setVState('asc'); } else { setVState('default'); } }, []); const dCycle = useCallback((currentDState) => { if (currentDState === 'default') { setDState('dsc'); setVState('default'); setRState('default'); } else if (currentDState === 'dsc') { setDState('asc'); } else { setDState('default'); } }, []); const rCycle = useCallback((currentRState) => { if (currentRState === 'default') { setRState('dsc'); setVState('default'); setDState('default'); } else if (currentRState === 'dsc') { setRState('asc'); } else { setRState('default'); } }, []);用
useMemo缓存处理后的数据
对Object.values(ratings).filter(...)的结果进行缓存,避免每次渲染重复执行过滤操作:const filteredRatings = useMemo(() => { return Object.values(ratings).filter(o => o.points.length); }, [ratings]);渲染时直接使用缓存后的数组:
<tbody> {filteredRatings.map((item) => { return ( <tr key={item.name}> <td className="variant">{item.name}</td> <td className='date'>{item.points.at(-1)[1] + 1 + "/" + item.points.at(-1)[2] + "/" + item.points.at(-1)[0].toString().slice(2, 4) }</td> <td className='rating'>{item.points.at(-1)[3]}</td> </tr> ) }) ?? null} </tbody>
修改后的完整组件代码
import './Recent.css'; import { React, useState, useEffect, useMemo, useCallback } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faSort, faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons' import * as fetch from 'node-fetch'; function Recent() { const [ratings, setRatings] = useState({}); const [headers, setHeaders] = useState([""]); const [loaded, setLoaded] = useState(false); const [vState, setVState] = useState('default'); const [dState, setDState] = useState('default'); const [rState, setRState] = useState('default'); const fetchRatings = () => { fetch('lichesslinkthatyoudontgettosee') .then (response => { return response.json() }) .then (data => { setRatings(data); setHeaders(["Variant", "Date", "Rating"]); setLoaded(true); }); } useEffect(() => { fetchRatings() }, []); const vCycle = useCallback((currentVState) => { if (currentVState === 'default') { setVState('dsc'); setDState('default'); setRState('default'); } else if (currentVState === 'dsc') { setVState('asc'); } else { setVState('default'); } }, []); const dCycle = useCallback((currentDState) => { if (currentDState === 'default') { setDState('dsc'); setVState('default'); setRState('default'); } else if (currentDState === 'dsc') { setDState('asc'); } else { setDState('default'); } }, []); const rCycle = useCallback((currentRState) => { if (currentRState === 'default') { setRState('dsc'); setVState('default'); setDState('default'); } else if (currentRState === 'dsc') { setRState('asc'); } else { setRState('default'); } }, []); const filteredRatings = useMemo(() => { return Object.values(ratings).filter(o => o.points.length); }, [ratings]); return ( <div className="container" style={{display: loaded ? 'flex' : 'none'}}> <h2>Most Recent</h2> <table> <thead> <tr> <th onClick={() => vCycle(vState)}>{headers[0]} <span><FontAwesomeIcon fixedWidth transform="shrink-3" icon={vState === 'default' ? faSort : vState === 'asc' ? faSortUp : faSortDown} /></span></th> <th onClick={() => dCycle(dState)}>{headers[1]} <span><FontAwesomeIcon fixedWidth transform="shrink-3" icon={dState === 'default' ? faSort : dState === 'asc' ? faSortUp : faSortDown} /></span></th> <th onClick={() => rCycle(rState)}>{headers[2]} <span><FontAwesomeIcon fixedWidth transform="shrink-3" icon={rState === 'default' ? faSort : rState === 'asc' ? faSortUp : faSortDown} /></span></th> </tr> </thead> <tbody> {filteredRatings.map((item) => { return ( <tr key={item.name}> <td className="variant">{item.name}</td> <td className='date'>{item.points.at(-1)[1] + 1 + "/" + item.points.at(-1)[2] + "/" + item.points.at(-1)[0].toString().slice(2, 4) }</td> <td className='rating'>{item.points.at(-1)[3]}</td> </tr> ) }) ?? null} </tbody> </table> </div> ); } export default Recent;
内容的提问来源于stack exchange,提问作者HoodedHacker
相关产品推荐
相关产品推荐

