React教程(十) : 性能优化 - useMemo, useCallback, memo

useMemo, useCallBack
这两个概念并非看上去那么容易理解,使用的不好的话,也很难带来任何的性能提升。

先说useMemo, 简单来说就是把返回值缓存起来,并监控一个变量。 如果被监控的变量不变,则返回值不变。以下是两个适用useMemo的场景

import React, { FC, useState, useMemo } from 'react';
export const UseMemoDemo:FC = (props) => {
    const [age, setAge] = useState(0);
    const [name, setName] = useState('Andy');

    function timeCostingFun(){
        console.log('call time costing fun');
        return `blablalba - ${name}`;
    }

    return(
        <>
            <div> <NameComponent name={name}></NameComponent> <button onClick={()=>setName(`${name}${age}`)}>Update Name</button> </div>
            <br />
            <div>{`age - ${ age }`} <button onClick={()=>setAge(age+1)}>Add age</button> </div>
            <br />
            <div>{timeCostingFun()}</div>
        </>
    );
}

export interface NameProps{
    name: string
}

const NameComponent:FC<NameProps> = (props) => {
    function getDescription(){
        console.log('call getName');
        return `my name is ${props.name}`;
    }

    let desc = getDescription();

    return(
        <div>
            {desc}
        </div>
    )
}

上例中,组件UseMemoDemo的timeCostingFun方法只与name有关,与age无关。但如果setAge,会触发render,也会再次调用timeCostingFun方法。
组件NameComponent的getDescription方法也会在父组件UseMemoDemo渲染的时候被重新渲染,而NameComponent也只与name有关,与age无关。
所以,这两处可以使用useMemo进行优化。优化修改后的代码如下所示:

import React, { FC, useState, useMemo } from 'react';

export const UseMemoDemo:FC = (props) => {
    const [age, setAge] = useState(0);
    const [name, setName] = useState('Andy');

    // function timeCostingFun(){
    //     console.log('call time costing fun');
    //     return `blablalba - ${name}`;
    // }

    let result =  useMemo(()=>{
        console.log('call time costing fun');
        return `blablalba - ${name}`;
    }, [name]);

    return(
        <>
            <div> <NameComponent name={name}></NameComponent> <button onClick={()=>setName(`${name}${age}`)}>Update Name</button> </div>
            <br />
            <div>{`age - ${ age }`} <button onClick={()=>setAge(age+1)}>Add age</button> </div>
            <br />
            {/* <div>{timeCostingFun()}</div> */}
            <div>{result}</div>
        </>
    );
}

export interface NameProps{
    name: string
}

const NameComponent:FC<NameProps> = (props) => {

    // function getDescription(){
    //     console.log('call getName');
    //     return `my name is ${props.name}`;
    // }

    // let desc = getDescription();

    let desc = useMemo(()=>{
        console.log('call getName');
        return `my name is ${props.name}`;
    }, [props.name]);

    return(
        <div>
            {desc}
        </div>
    )
}

上面这个示例非常的简单,因为useMemo所监控的变量类型都是原生类型(primitive),如果是Object类型呢?
于是我们对上述代码做如下修改,首先是子组件

为其props增加一个Object类型的字段,并让useMemo监控该字段。

其父组件做如下的修改

此时我们click Add age按钮,会发现即使user的值没有变化,子组件useMemo还是更新了。 这是因为父组件点击了Add age按钮后,重新渲染,并重新生成了一个userInitData,其值和之前的userInitData完全一样,但引用不一样,所以子组件的useMemo还是认为两者不等。
有个方案可以解决该问题
1: 将user对象展开为原生类型,如下图所示:

2: 将传入子组件的user值缓存起来(useState, useMemo都可以,具体看应用场景而定)

这样子组件的useMemo可以保持不变,继续监控props.user

参考:https://jancat.github.io/post/2019/translation-usememo-and-usecallback/

理解了useMemo之后,useCallback也就容易多了。 useMemo也是监控一组变量,只不过与useMemo不同,其返回的是方法。看下面的例子:

import React, { FC, useState, useCallback } from 'react';

export const UseCallbackDemo:FC = (props) => {
    const [age, setAge] = useState(0);
    const [name, setName] = useState('Andy');

    return(
        <>
            <div> <NameComponent name={name} click={()=>{
                console.log('click name button');
            }}></NameComponent></div>
            <br />
            <div>{`age - ${ age }`} <button onClick={()=>setAge(age+1)}>Add age</button> </div>
           
        </>
    );
}

interface NameProps{
    name: string,
    click: ()=>void
}

const NameComponent:FC<NameProps> = (props) => {
    console.log('call name component')
    return(
        <button onClick={props.click}>
           {props.name}
        </button>
    )
}

子组件NameComponent接受click方法作为props的一部分。该demo中,点击夫组件的Add age按钮,还是会导致子组件刷新。 如果做到子组件的props不变,子组件就不重新渲染呢?
答案是:使用React.memo和useCallback。 代码示例如下:


React.memo的作用就是缓存一个function,如果其props不变,就返回缓存值。

完整源码地址:https://github.com/992990831/modernization/tree/main/performance-tuning

posted @ 2020-11-07 00:07  老胡Andy  阅读(550)  评论(0编辑  收藏  举报