实现局部展开/收起功能 避免React组件整体重渲染的解决方案
解决Show More/Show Less功能的组件重渲染问题
需求与问题
- 需求:为每个
<div>和<p>实现独立的show more/show less功能,点击对应按钮仅展开当前内容,避免整体组件重渲染 - 当前问题:使用
useState在父组件维护状态数组时,点击按钮会触发所有子组件重渲染,导致性能损耗
现有代码示例
父组件
const [arr, setArr] = useState([]) const onCl = (e) => { setArr((prev) => [...prev, e.target.value]) } return ( divarray.map((i, j) => { console.log("tdic") // 每次点击都会触发所有子组件的这个日志,说明全量重渲染 return ( <Commentbox divarr={arr[j]} value={j} oncl={(e) => onCl(e)} /> ) }) )
子组件(Commentbox)
return ( <div> div{j} {/* some icons here */} {divarr && <div> right side div </div>} <button onClick={(e) => {oncl(e)}} value={j} >see more</button> </div> )
问题分析
父组件用数组arr维护所有子组件的展开状态,每次更新都会生成新数组,导致所有Commentbox子组件因为divarr或oncl属性变化触发重渲染——即使只有一个子组件的状态改变,其他无关子组件也会跟着重新渲染。
解决方案
方案1:让子组件独立管理自身状态
把展开状态移到子组件内部,每个子组件自己维护是否展开,父组件完全不参与状态管理,从根源避免父组件更新导致的全量重渲染。
修改后的子组件:
const Commentbox = ({ j }) => { const [isExpanded, setIsExpanded] = useState(false) return ( <div> div{j} {/* some icons here */} {isExpanded && <div> right side div </div>} <button onClick={() => setIsExpanded(!isExpanded)} > {isExpanded ? 'see less' : 'see more'} </button> </div> ) }
父组件简化为:
return ( divarray.map((i, j) => { return <Commentbox key={j} j={j} /> }) )
这种方式下,每个子组件的状态变化只会触发自身重渲染,完全不影响其他子组件和父组件,性能最优。
方案2:React.memo+父组件状态优化(适合需父组件同步状态的场景)
如果必须在父组件维护状态,可通过以下方式减少重渲染:
- 用对象而非数组存储状态,避免每次更新生成新数组导致所有子组件的
divarr属性变化 - 用
useCallback缓存点击回调,避免每次父组件渲染生成新函数 - 用
React.memo包裹子组件,只有当props真正变化时才重渲染
修改后的父组件:
const [expandedMap, setExpandedMap] = useState({}) // 用useCallback缓存回调,避免每次父组件渲染生成新函数 const onCl = useCallback((index) => { setExpandedMap(prev => ({ ...prev, [index]: !prev[index] })) }, []) return ( divarray.map((i, j) => { return ( <Commentbox key={j} isExpanded={expandedMap[j]} index={j} oncl={onCl} /> ) }) )
修改后的子组件:
// 用React.memo包裹,只有props变化时才重渲染 const Commentbox = React.memo(({ isExpanded, index, oncl }) => { console.log(`渲染div${index}`) return ( <div> div{index} {/* some icons here */} {isExpanded && <div> right side div </div>} <button onClick={() => oncl(index)} > {isExpanded ? 'see less' : 'see more'} </button> </div> ) })
这种方式下,只有被点击的子组件的isExpanded属性会变化,其他子组件的props不变,因此不会重渲染。
方案3:原生DOM操作(适合非复杂交互场景)
如果不需要React状态同步,可直接用原生DOM控制内容显示隐藏,完全绕过React的渲染机制:
const Commentbox = ({ j }) => { const contentRef = useRef(null) const toggleContent = () => { if (contentRef.current) { contentRef.current.style.display = contentRef.current.style.display === 'none' ? 'block' : 'none' } } return ( <div> div{j} {/* some icons here */} <div ref={contentRef} style={{display: 'none'}}> right side div </div> <button onClick={toggleContent} >see more</button> </div> ) }
这种方式完全不涉及React状态更新,因此不会触发任何组件重渲染,性能最好,但缺点是无法和React状态联动,适合简单的显示隐藏场景。
内容的提问来源于stack exchange,提问作者Harsha
相关产品推荐
相关产品推荐

