React组件性能优化
合理使用state
-
减轻state:只存储跟组件渲染相关的数据(比如:count/给后端的入参/后端返回数据data/切换状态visible、loading等)
-
注意:不用做渲染的数据不要放在state中(比如:定时器timer/用作js处理数据定义的变量)
-
对于需要在多个方法中用到的数据,应该放在this中
class He1lo extends Component { componentDidMount () // timerId存储到this中,而不是state中 this. timerId - setInterval(() - {}, 2000) } componentWillUnmount() { clearInterval (this.timerId) } render() { ... } }
避免不必要的重新渲染
- 组件更新机制:父组件更新会引起子组件也被更新
- 问题:子组件没有任何变化时也会重新渲染,如何避免不必要的重新渲染?
- 解决方式:使用钩子函数shouldComponentUpdate(nextProps,nextState)
- 作用:通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染
- 触发时机:更新阶段的钩子函数,组件重新渲染前执行
同页面state是否改变
shouldComponentUpdate(nextProps,nextState) {
return nextState.pageSize !== this.state.pageSize
}
父子组件props是否改变
shouldComponentUpdate(nextProp,nextState){
return nextProp.text !== this.props.text
}
上述的比较只是‘浅层比较’,如果类型是基本类型,只要值相同,那么“浅层比较”,对于复杂对象咋整?
对于复杂对象,‘浅层比较’的方式只看这两个prop是不是同一个对象的引用,如果不是,哪怕对象中的内容完全一样也会认为是不同的两个prop。所以把复杂对象提出来,不要放在render函数中,因为每次渲染都会重新创建对象,导致引入地址不同,infoProps每次都会不同
错误写法
<father infoProps = {{ name: 'jack' }} />
正确写法
const infoObj = {name: "jack"};//确保这个初始化只执行一次,不要放在render函数中
<father infoProps = {infoObj} />
函数式组件优化useCallback、React.memo、useMemo
优化思路:
减少重新 render 的次数。因为在 React 里最重(花时间最长)的一块就是 reconction(简单的可以理解为diff),如果不 render,就不会 reconction。 减少计算的量。主要是减少重复计算,对于函数式组件来说,每次 render都会重新从头开始执行函数调用。
React.memo
父组件点击事件改变了数据,render重新执行,会将其他子组件重新渲染,所以用React.memo包裹子组件
import {useState, memo} from 'react'
import Child from "./Child";
const Index = ()=>{
const [subTitle, setSubTitle] = useState('子组件')
const updateSubTitle = ()=>{
setSubTitle('修改子组件')
}
return (
<div>
<div>{subTitle}</div>
<button onClick={updateSubTitle}>修改子标题</button>
<Child/>
</div>
);
}
export default memo(Index);
useCallback
父组件点击事件改变了数据,render重新执行,子组件被React.memo包裹,所以不会重新渲染,但是父组件有点击
事件传给了子组件,又会重新渲染子组件,因此用useCallback包裹点击函数
// 父组件
const Index = ()=>{
const [subTitle, setSubTitle] = useState('子组件')
const updateSubTitle = ()=>{
setSubTitle('修改子组件')
}
const handlerClick = useCallback(()=>{
console.log('子组件点击')
},[])
return (
<div>
<div>{subTitle}</div>
<button onClick={updateSubTitle}>修改子标题</button>
<Child onClick={handlerClick}/>
</div>
);
}
// Child子组件
const Child = (props)=>{
console.log('我是子组件')
return (
<div>
<div>我是子组件</div>
<button onClick={props.onClick}>子组件按钮</button>
</div>
)
}
export default React.memo(Child)
useMemo
当组件内部某一渲染的数据,要通过计算得到,这个计算依赖于特定的state或props,我们就用useMemo来缓存这个渲染的数据,以致于我们在修改它们没有依赖的数据源的情况下,多次调用这个计算函数,浪费性能
例: val发生改变,我们不想执行本身的getNum函数,导致多次计算,用useMemo依赖项count
function Example() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const getNum = useMemo(() => {
return count * 100
}, [count])
return <div>
<h4>总和:{getNum()}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
}
React组件复用
- 思想:复用相似功能(函数组件封装思想)
- 复用内容:1.state 2.操作state的方法
- 方式:1.render props模式 2.高阶组件(HOC)
Key的使用
- key属性可以明确的告诉React每个组件的唯一标识
- 在选取Key值时尽量不要用index,因为如果当数据的添加方式不是顺序添加,而是以其他方式(逆序,随机等),会导致每一次添加数据,每一个数据值的索引号都不一样,这就导致了Key的变化,而当Key变化时,React就会认为这与之前的数据值不相同,会多次执行渲染,会造成大量的性能浪费。
<ul>
tabData.map((item,index) => {
<TabItem
key={item.id} //最好别使用index,除非后端没有唯一id
text={item.text}
/>
})
</ul>