React优化渲染思路
前言
在React常见的开发下,让人最影响深刻的就是re-render带来的额外开销,心智负担相比Vue来说剧增,下面来总结几条在实际开发中常见的优化思路
useMemo useCallback缓存依赖
下面来看一个demo
import React, { useCallback, useState } from 'react';
import style from './index.less'
function App() {
const [count, setCount] = useState(0)
const countAdd = useCallback(() => {
console.log('执行了函数')
setCount(count + 1)
}, [])
console.log(`re-render times: 该组件重新渲染了`)
return (
<div className={style.wrapper + ' main'}>
<button onClick={countAdd}>+</button>
<button >-</button>
<span>{count}</span>
</div>
);
}
export default App;
useCallback这个函数缓存只是会记忆当前的函数体,但是如果遇到setState操作则是获取不到最新值,因为setState返回了相同的值所以并不会触发render,此时获取到的count不是当前的count,而是他的初始值,我们换一种写法答案就显而易见了
const countAdd = useCallback(() => {
console.log('执行了函数')
setCount(pre => {
console.log(count)
return count + 1
})
}, [])
所以当涉及到同步代码而且没有setState时,除非真的出现了性能瓶颈,一般不建议以useCallback为核心进行优化
如果把return中的count换成pre,那是会执行re-render,因为这个pre是不受外部state控制的,单独传递preValue的一个值。所以在使用过程中如果涉及到setState操作,需要考虑是否需要使用useCallback,因为这个写法相等于const countAdd = () => setCount(count + 1) ,往依赖项中添加count和这个实现的效果相同,没必要在代码层中引入无意义的写法
useMemo官方的话术来说是对函数的返回值进行缓存,下面是一个简单的demo
function App() {
const [count, setCount] = useState(0)
const [_, setState] = useState({})
const calcCountDouble = useMemo(() => {
console.log(`calc重新计算`)
return 2 * count
}, [count])
const countAdd = () => setCount(count + 1)
const reRender = () => setState({})
console.log(`re-render times: 该组件重新渲染了`)
return (
<div className={style.wrapper + ' main'}>
<button onClick={countAdd}>+</button>
<button onClick={reRender}>-</button>
<span>{count}</span>
<span>{calcCountDouble}</span>
</div>
);
}
这块代码也与下面的写法相等
function App() {
const [count, setCount] = useState(0)
const [_, setState] = useState({})
const countAdd = () => setCount(count + 1)
const reRender = () => setState({})
console.log(`re-render times: 该组件重新渲染了`)
return (
<div className={style.wrapper + ' main'}>
<button onClick={countAdd}>+</button>
<button onClick={reRender}>-</button>
<span>{count}</span>
<span>
{
useMemo(() => {
console.log(`calc重新计算`)
return 2 * count
}, [count])
}
</span>
</div>
);
}
好处就是不需要在函数里定义变量去接收,但是也会有坏处,那就是会使代码变得难以维护,jsx写起来会比较庞大,如果对一般业务开发的同学而言在接手时可能会有上手难度存在
父子组件缓存
通常情况下,当父组件内部发生re-render时,会默认带着子组件一起render,如果你使用memo包裹子组件,或者是PureComponent时,子组件不会被父组件的render影响,只有当子组件的props发生变化才会被外部re-render
我们主要介绍另外一种父子组件缓存模式,使用children,下面的代码实现起来其实和memo的效果是一样的
function App() {
return (
<Father>
<Child />
</Father>
);
}
const Father = ({ children }) => {
const [count, setCount] = useState(0)
const [_, setState] = useState({})
const countAdd = () => setCount(count + 1)
const reRender = () => setState({})
console.log(`re-render times: 该组件重新渲染了`)
return (
<div className={style.wrapper + ' main'}>
<button onClick={countAdd}>+</button>
<button onClick={reRender}>-</button>
<span>{count}</span>
{children}
</div>
)
}
const Child = () => {
console.log(`Child组件重新渲染了`)
return <div>Child</div>
}
这是一种很巧妙避开re-render的一种开发方式,我也更加推荐使用这个方式去构建组件,将UI层和逻辑层抽离,减少re-render,这在组件库中是一个很重要的思想
转载自:https://juejin.cn/post/7374595069745692698