16.6以前,函数组件没有像
shouldComponentUpdate
这样的方法,也没有相似PureComponent
这种解决方案,避免不了函数组件里面全部的代码再次的执行,要依靠外面的条件渲染来控制,或者是高阶组件。以前的话,选择使用函数组件的状况是一些比较简单的又比较纯的组件,只是负责展现的。并且函数组件最终编译babel结果是只执行createElement
那一步;class组件同样有生命周期要实例化,最终通过Babel成es5代码的时候还很长前端
当16.6的memo问世,函数组件就有了相似PureComponent
和shouldComponentUpdate
的解决方案,memo的使用方法:react
const C = (props) => {
return <section>那一晚上{props.name}的嫂子真美</section>
}
export default React.memo(C)
复制代码
当父组件执行render的时候,避免不了C组件的渲染和C函数的执行(若是不在外面加判断的话:{isShowC && <C />}
)。当到了C组件的时候,会浅比较C组件先后props值。若是props每个属性值都同样,会跳过函数组件C的执行,减小了没必要要的渲染,达到了性能优化。数组
第二个参数,是一个函数,该函数传入参数是新props和上次props,咱们能够在函数里面作判断逻辑,控制返回值。当咱们让函数return true的时候,告诉了react这两个props是同样的,不用从新执行整个函数组件;反之false的时候会从新执行该组件性能优化
memo(IfEqual, () => false);
复制代码
好比这行代码,判断函数一直返回false,memo
包住的IfEqual
组件不管怎样都会从新执行babel
当咱们用上了memo,就能够根据业务来进行优化了:函数
React.memo(C, (nextProps, prevProps) => {
// 作咱们想作的事情,相似shouldComponentUpdate
})
复制代码
咱们都知道,js中函数不是简单数据类型,也就是说function(){}
和function(){}
是不同的,与{}
和{}
不同同理。那么咱们传入props.onClick
(即便是长得同样的内容彻底同样),先后props.onClick
都不能划上等号性能
<div>
<IfEqual onClick={() => {}} /> </div>
复制代码
以为inline function很差看,那前面定义一下,实际上仍是逃不了同一个事情:它们是不同的。此次是由于,函数组件的渲染,也就是执行,每一次从新执行,函数做用域里面一切都是从新开始。这就至关于上一次组件渲染const handleClick = () => {}
,后面渲染又一次const handleClick = () => {}
,它们都不是同一个东西学习
export default () => {
const handleClick = () => {}
return (
<div> <IfEqual onClick={handleClick} /> </div> ) } 复制代码
这种状况下,咱们能够用memo第二个参数来拯救多余一次的渲染的局面:优化
// props: { a: 1, onClick: () => {} }
// 在咱们知道onClick是作同一个事情的函数的前提下,不比较onClick
React.memo(C, (nextProps, prevProps) => nextProps.a === prevProps.a)
复制代码
最后,先后props的onClick
,它们只有一种状况是同样的——把声明抽到组件外面去ui
const handleClick = () => {}
export default () => {
return (
<div> <IfEqual onClick={handleClick} /> </div> ) } 复制代码
这时候,有没有想起class组件里面老是onClick={this.handleClick}
呢?this.handleClick
一直都是同一个函数。这种状况,子组件为函数组件的时候,包一层memo就能够实现purecomponent的效果
函数组件把函数定义写在外面,是能够解决问题。可是,若是handleClick依赖组件内部的一些变量,那handleClick又不得不写在里面(固然利用引用类型能够解决)。或者仍是正常写,靠memo第二个参数来控制要不要从新渲染子函数组件。可是不管怎样,都存在一个问题,就是那一块代码写在里面呢,都没法避免代码的执行和函数的从新定义,好比
function a(){
const b = function(){
console.log(1)
// 不少不少代码
}
}
a()
a() // 函数b又被定义了一次
复制代码
若是咱们经过依赖来肯定先后两次是否是同一个函数,咱们能够用函数记忆来实现整个功能
let prev
let prevDeps
function memorize(fn, deps) {
// 先后依赖一致,不用从新计算直接返回上次结果
if (prev && isEqual(deps, prevDeps)) {
return prev
}
prevDeps = deps
prev = fn
return fn
}
function a(){
const b = memorize(function(){
console.log(1)
// 不少不少代码
}, [])
}
a()
a() // 函数b又被定义了一次
复制代码
相似函数记忆的原理,后来有了useCallback
的出现,多了一种新的解决方案,根据依赖生成一个函数:
const handleClick = useCallback(() => {
console.log(dep)
}, [dep])
复制代码
当dep不变,每一次函数组件的执行,handleClick都是同一个函数。若是dep变了,那么handleClick又是一个新的函数
export default () => {
// 没有依赖,永远是同一个函数
const handleClick = useCallback(() => {}, []);
// 依赖a,从新执行函数组件,a不变的,是同一个函数
// a变了handleClick是新的函数
const handleClick1 = useCallback(() => {}, [a]);
return (
<div> <IfEqual onClick={handleClick} /> </div> ) } 复制代码
react组件也是一个函数,那其实useCallback
还能够作一个函数组件:
export default () => {
const handleClick = useCallback(() => {}, []);
const Cpn = useCallback(({ name }) => {
return <button onClick={handleClick}>{name}</button>
}, [handleClick]);
return (
<div> <Cpn name="hi" /> </div> ) } 复制代码
固然这只是一个简单的场景,若是用了hooks,尚未解决问题或者暂时没有想到优雅的封装技巧,想用高阶组件的时候,不妨尝试一下useCallback
const a = useMemo(() => memorizeValue, deps)
复制代码
当deps不变,a的值仍是上次的memorizeValue,省去了从新计算的过程。若是memorizeValue是一个函数,和useCallback是同样的效果:
useCallback(fn, inputs) <=> useMemo(() => fn, inputs)
咱们能够试一下同步执行的代码,当时间很是长的时候,useMemo能够发挥它的做用了:
// 强行更新组件
const useForceUpdate = () => {
const forceUpdate = useState(0)[1];
return () => forceUpdate(x => x + 1);
}
// 一个很耗时间的代码
function slowlyAdd(n) {
console.time('add slowly')
let res = n;
for (let i = 0; i < 2000000000; i++) {
res += 1;
}
console.timeEnd('add slowly')
return res;
}
// useMemo记忆结果的一个自定义hook
function useSlowlyAdd(n) {
const res = useMemo(() => {
return slowlyAdd(n);
}, [n])
return res;
}
export default () => {
const [count, add] = useState(1);
const forceUpdate = useForceUpdate();
const handleClick = useCallback(() => {}, []);
useSlowlyAdd(count) // 第一次这里会耗不少时间,页面卡死一阵
return (
<> <button onClick={forceUpdate}>更新页面</button> <button onClick={() => add(count + 1)}>+</button> </> ) } 复制代码
第一次进来,页面暂时没有任何反应一阵,这是由于slowlyAdd占用了js主线程。当咱们点击‘更新页面’更新的时候,页面并无卡死,并且组件也从新渲染执行了一次。当咱们点击+,页面又开始卡死一阵。
这是由于点击+的时候,修改了useMemo的依赖n,n变了从新计算,计算耗费时间。若是点击更新页面,没有修改到依赖n,不会从新计算,页面也不会卡
固然,useMemo也能够作高阶组件,用起来的时候,能够写成reactElement的形式了:
const HOC = useMemo(() => <C />, deps)
复制代码
有以下的组件,Big是一个10w个节点的组件,每个节点都绑定事件
const handleClick = useCallback(() => {}, []);
export default () => {
return (
<div> <Big onClick={handleClick} /> </div> ) } 复制代码
若是Big组件没有memo包住,首次挂载和再次渲染父组件性能以下:
若是Big组件有memo包住并且props被认为是同样的状况下,首次挂载和再次渲染父组件性能以下:
总结一下对于props的某个属性值为函数的时候,如何作到子组件不从新执行多余渲染:
关注公众号《不同的前端》,以不同的视角学习前端,快速成长,一块儿把玩最新的技术、探索各类黑科技