ReactJS中如何避免子组件重复渲染?
下面是几种实用的解决方案,附代码示例,帮你快速理解:
1. React.memo() 配合浅比较
React.memo是一个高阶组件,它会对组件的props进行浅比较:如果前后props没有变化,就跳过子组件的重新渲染。
代码示例
子组件(用React.memo包裹)
import React from 'react'; const ChildComponent = React.memo(({ name }) => { console.log('子组件渲染了'); return <div>Hello, {name}</div>; }); export default ChildComponent;
父组件
import React, { useState } from 'react'; import ChildComponent from './ChildComponent'; const ParentComponent = () => { const [count, setCount] = useState(0); const [userName] = useState('Alice'); return ( <div> <button onClick={() => setCount(count + 1)}>点击计数:{count}</button> <ChildComponent name={userName} /> </div> ); }; export default ParentComponent;
点击按钮改变count时,子组件的name props没有变化,所以子组件不会重新渲染,控制台不会打印"子组件渲染了"。
2. 自定义比较函数(处理复杂props)
当props是对象、数组这类引用类型时,浅比较会因为引用变化判断为props更新,这时候可以给React.memo传入第二个参数,自定义比较逻辑。
代码示例
子组件
import React from 'react'; const ChildComponent = React.memo(({ user }) => { console.log('子组件渲染了'); return <div>用户姓名:{user.name},年龄:{user.age}</div>; }, (prevProps, nextProps) => { // 自定义比较:只判断user的内容是否一致 return prevProps.user.name === nextProps.user.name && prevProps.user.age === nextProps.user.age; }); export default ChildComponent;
父组件
import React, { useState } from 'react'; import ChildComponent from './ChildComponent'; const ParentComponent = () => { const [count, setCount] = useState(0); return ( <div> <button onClick={() => setCount(count + 1)}>点击计数:{count}</button> {/* 每次渲染创建新对象,但自定义比较会判断内容相同 */} <ChildComponent user={{ name: 'Bob', age: 25 }} /> </div> ); }; export default ParentComponent;
即使父组件每次渲染都传入新的user对象,只要内容和之前一致,子组件就不会重新渲染。
3. useCallback() 缓存函数props
如果父组件传给子组件的是函数,父组件每次渲染都会创建新的函数实例,导致React.memo失效。用useCallback可以缓存函数的引用,保持props不变。
代码示例
子组件
import React from 'react'; const ChildComponent = React.memo(({ onButtonClick }) => { console.log('子组件渲染了'); return <button onClick={onButtonClick}>子组件按钮</button>; }); export default ChildComponent;
父组件
import React, { useState, useCallback } from 'react'; import ChildComponent from './ChildComponent'; const ParentComponent = () => { const [count, setCount] = useState(0); // 用useCallback缓存函数,依赖项为空时,函数引用始终不变 const handleChildClick = useCallback(() => { console.log('子组件按钮被点击'); }, []); return ( <div> <button onClick={() => setCount(count + 1)}>点击计数:{count}</button> <ChildComponent onButtonClick={handleChildClick} /> </div> ); }; export default ParentComponent;
handleChildClick的引用在父组件渲染时保持不变,所以子组件的props没有变化,不会重新渲染。
4. useMemo() 缓存复杂计算的props
如果传给子组件的是经过计算的数组、对象等复杂值,父组件每次渲染都会重新计算这些值,导致props引用变化。用useMemo可以缓存计算结果,保持引用不变。
代码示例
子组件
import React from 'react'; const ChildComponent = React.memo(({ filteredList }) => { console.log('子组件渲染了'); return ( <ul> {filteredList.map(item => <li key={item.id}>{item.name}</li>)} </ul> ); }); export default ChildComponent;
父组件
import React, { useState, useMemo } from 'react'; import ChildComponent from './ChildComponent'; const ParentComponent = () => { const [count, setCount] = useState(0); const [list] = useState([ { id: 1, name: '苹果', isActive: true }, { id: 2, name: '香蕉', isActive: false }, { id: 3, name: '橙子', isActive: true } ]); // 用useMemo缓存过滤后的数组,只有list变化时才重新计算 const filteredList = useMemo(() => { return list.filter(item => item.isActive); }, [list]); return ( <div> <button onClick={() => setCount(count + 1)}>点击计数:{count}</button> <ChildComponent filteredList={filteredList} /> </div> ); }; export default ParentComponent;
父组件改变count时,filteredList的引用不变,子组件不会重新渲染。
5. 拆分组件,下沉状态
把父组件中不影响子组件的状态,拆分到独立的子组件中,让子组件的直接父组件只包含和它相关的状态,这样状态变化就不会触发子组件的重渲染。
代码示例
import React, { useState } from 'react'; import ChildComponent from './ChildComponent'; // 拆分出只管理count的组件 const CountComponent = () => { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>点击计数:{count}</button>; }; const ParentComponent = () => { const [userName] = useState('Charlie'); return ( <div> <CountComponent /> <ChildComponent name={userName} /> </div> ); }; export default ParentComponent;
CountComponent的状态变化不会影响ParentComponent的渲染,因此ChildComponent也不会被重新渲染。
内容的提问来源于stack exchange,提问作者Abidur Rahman Chowdhury

