React优化篇-memo,useMemo,useCallback

React.memo

当16.6的memo问世,函数组件就有了类似PuerComponent和shouldComponentUpdate的解决方案,memo的使用方法:

const C = (props) => {
 return 

那一夜{props.name}真美

} export default React.memo(C)

当父组件执行render的时候,避免不了C组件的渲染和C函数的执行(如果不在外面判断的话{isShowC && })。当到了C组件的时候,会浅比较C组件前后props值。如果props每一个属性值都一样,会跳过函数组件C的执行,减少了不必要的渲染,达到了性能优化。

memo第二个参数

第二个参数,是一个函数,该函数传入参数是新props和上次props,我们可以在函数里面做判断逻辑,控制返回值。当我们让函数return true的时候,告诉react这两个props是一样的,javascript不用重新执行整个函数组件。反之false的时候会重新执行该组件

memo(IfEqula,() => false);

比如这行代码,判断函数一致返回false,memo包住的IfEqula组件无论怎样都会重新执行 当我们用上了memo,就可以根据业务来进行优化了:

React.memo(C,(nextProps,prevProps) => {
 // 做我们想做的事情,类似shouldComponentUpdate
})

函数组件中传入props值为函数时

我们都知道,js中函数不是简单数据类型,也就是说function(){}和function(){}是不一样的,与{}和{}不一样同理。那么我们传入props.onClick(即使是长得一样的内容完全一样),前后props.onClick都不能划上等号

{}}/>

觉得inline function不好看,那前面定义一下,实际上还是逃不了同一件事情:它们是不一样的。这是因为,函数组件的渲染,也就是执行,每一次重新执行,函数作用域里面一切都是重新开始。这就相当与上一次组件渲染const handelClick = () =>,后面渲染又一次const handleClick = () => {} ,它们都不是同一个东西

export default () => {
 const handleClick = () => {};
 return(
 
 )
}

这种情况下,我们可以用memo第二个参数来拯救对于一次的渲染的局面

// props : { a: 1 , onClick : () => {} }
 // 在我们知道onClick是做同一个事情的函数的前提下,不比较onClick
React.memo(C , (nextProps,prevProps) => nextProps.a === prevProps.a)

最后,前后props的onClick,它们只有一种情况是一样的,把声明抽象到组件外面去

const handerClick = () => {};
export defalut () => {
 return (
 

) };

这时候,有没有想起class组件里面总是onClick={this.handerClick}呢?this.handleClick一直都是同一个函数。这种情况,子组件为函数组件的时候,包一层memo实现PureComponent的效果

useCallback

函数组件把函数定义写在外面,是可以解决问题。但是,如果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 handeClick = useCallBack(() => {
 console.log(dep)
},[dep])

当dep不变,每一次函数组件的执行,handelClick都是同一个函数。如果dep变了,那么handleClick又是一个新的函数。

export default () => {
 // 没有依赖,永远是同一个函数
 const handleClick = useCallBack(() => {},[]);
 // 依赖a,重新执行函数组件,a不变的,是同一个函数
 // a变了handleClick是新的函数
 const handelClick1 = useCallBack(() => {},[a]);
 return(
 

) }

react组件也是一个函数,那其实useCallback还可以做一个函数组件:

export default () => {
 const handleClick = useCallback(() => {},[]);
 const Cpn = useCallback(({name}) => {
 return 
 },[handleClick])
 return (
 

) }

当然这只是一个简单的场景,如果用了hooks,还没有解决问题或者暂时没有想到优雅的封装技巧,想用高阶组件的时候,不妨尝试一下useCallback

useMemo

const a = useMemo(() => memorizeValue,deps);

当deps不变,a的值还是上次的memorizeValue,省去了重新计算的过程。如果memorizeValue是一个函数,和useCallback是一样的效果

useCallback(fn,deps) <=> useMemo(() => fn ,deps)

我们可以试一下同步执行的代码,当时间非常长的时候,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(
 <>
 
 
 <>
 )
}

第一次进来,页面暂时没有人设反应一阵,这是因为slowlyAdd占用了住县城。当我们点击‘更新页面’更新的时候,页面并没有卡斯,而且组件也重新渲染执行了一次。当我们点击+,页面又开始卡死一阵。

这是因为点击+的时候,修改了useMemo的依赖n,n变了重新计算,计算耗费时间。如果点击更新页面,没有修改到依赖n,不会重新计算,页面也不会卡。

当然useMemo也可以做高阶组件,用起来的时候,可以写成reactElement的形式了

const HOC = useMemo(() => ,deps)

收藏

举报


分享到:


相關文章: