React Hook使用之useMemo和useCallback

什么是useMemo?

useMemo函数可以缓存已有的计算结果,在组件发生更新进行重新渲染时,会采用已有的缓存结果,而不是重新进行计算,从而达到性能优化的效果。

如果你使用过Vue,那么useMemo就和Vue 中的computed类似。

//useMemo有两个参数
//第一个参数factory:用来处理业务逻辑的计算函数,通常是计算比较占用资源的函数
//第二个参数deps:依赖列表,当依赖列表中的依赖项发生改变时,会重新调用factory函数进行计算
function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;

useMemo的用法?

//computeExpensiveValue值计算比较占用性能的函数
//a,b为依赖项,当a,b的值发生改变时,在重新渲染的时候会重新计算computeExpensiveValue
//这种优化有助于避免在每次渲染时都进行高开销的计算。
const [a,setA]=useState(0);
ocnst [b,setb]=useState(100000000000);

const memoizedValue = useMemo(() => computeExpensiveValue, [a, b]);

//a,b改变时重新计算
cosnt computeExpensiveValue=()=>{
    let sum=0;
    for(let i=a,i<b;i++)
        {
            sum=sum+i;
        }
    return sum;
    //return <div>{sum}<div>//或者返回渲染元素
    //
}

//调用 memoizedValue

记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如清除副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo

如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

//computeExpensiveValue函数在组件每次渲染事都会调用
//没必要useMemo()
const memoizedValue = useMemo(() => computeExpensiveValue(a, b));

//computeExpensiveValue函数只计算一次
const memoizedValue = useMemo(() => computeExpensiveValue(a, b),[]);

**你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。**将来,React 可能会选择“遗忘”以前的一些 已经缓存的 值,(即使你使用了useMemo)并在下次渲染时重新计算它们,比如为离屏组件(一些不在可视范围内没有必要渲染的组件)释放内存。

优化代码性能的方法:先编写在没有 useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo,以达到优化性能的目的。

useMemoReact.memo的优化方式,都是通过缓存的方式进行优化,React.memo是高阶u组件爱你,通过React.memo()包裹组件,可以让组件只在props的值发生改变(默认进行浅比较,第二个参数可以指定比较方式)时,才会重新渲染组件。

什么是useCallback?

useCallbackuseMemo比较相似,只不过useMemo缓存的是值,useCallback缓存的是函数。把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新,否则在组件重新渲染的时候,该函数也不会重新生成,而是采用已经缓存的版本,这点很重要。

//callback:内联回调函数
//deps:重新生成函数对象的依赖项
//返回值:生产一个新的函数,功能和内联回调函数的功能一样
function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;

useCallback用法

下面有一个简单的例子

function APP() {

    const [value, setValue] = useState(123)
    const [otherValue, setOtherValue] = useState(123)

    const changeValue = useCallback(() => {
        setValue(value => value+1)
    }, [value])
    
//  点击父组件的按钮,父组件的数据发生更新,子组件不会重新渲染
    return (
        <div>
            <div>测试数据==={otherValue}</div>       
            <button onClick={() => setOtherValue(value => value-=5)}>改变无关的数据,引起组件更新</button>
            <Message value={value} changeValue={changeValue} />
        </div>
    )
}

const Message = memo(
    function Message({value, changeValue}) {
        console.log('渲染了');
        //点击子组件的按钮,子组件的值value发生改变了,子组件会重新渲染
        return (
            <div>
                <button onClick={changeValue}>改变有关数据</button>
                <p>与Message渲染有关的数据{value}</p>
            </div>
        )
    }
)

代码解释:

我们知道reacthook组件在stateprops发生改变时,会重新渲染组件,从而导致子组件也会被重新渲染,但有些时候,子组件的的propsstate值没有变化,子组件就没必要重新渲染(万一子组件渲染比较耗时呢)这样就可以进行优化。所有使用React.memo函数包裹子组件,这样在传递给子组件的props的值没有改变时,子组件就不会重新被渲染,就是Message组件 propsvalue属性的值没有发生改变,Message组件就应该不会重新渲染。

但是Message组建的props中还有一个函数属性changeValue,在副组件重新渲染时候,会重新生成一个changValue函数对象,导致传入Message组件的changeValue对象发生改变,从而引起Message组件重新渲染。我们是不希望父组件中changeValue函数重新生成的,第一它在重新渲染时没有什么改变,第二它重新生成还会导致子组件被重新渲染。那么我们可以把这个函数的功能缓存下来,每次使用缓存的函数,就不会导致重新渲染了,useCallback就是做这个的。

//会缓存函数状态,函数不会改变 ,函数中value的值永远也不会改变,是初始值缓存的状态
const changeValue = useCallback(() => {
        setValue(value => value+1)
    }, [])
//会缓存函数状态,在value改变时,改变函数状态 ,函数中value的值也会改变
//注意,如果useCallback中函数使用到了父组件state中的变量,一定要放在依赖数组中,如value
//否则 缓存的函数的value值永远为初始状态的值
const changeValue = useCallback(() => {
        setValue(value => value+1)
    }, [value])
//不会缓存函数,每次组件重新渲染时都会重新生成一个新的changeValue函数对象,引起props发生变化
 const changeValue = () => {
        setValue(value => value+1)
    }

一般useCallback函数会配合React.memo()函数一起使用的,利用缓存函使子组件不会重新渲染,如果不使用React.memo()函数,子组件是会重新渲染的。

React.memo函数

React.memo 为高阶组件。

如果你的组件在相同 props的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

//第一个参数为要包裹的组件
//第二个参数为前后props值的比较方式判断props是否改变,默认是浅比较
  function memo<T extends ComponentType<any>>(
        Component: T,
        propsAreEqual?: (prevProps: Readonly<ComponentProps<T>>, nextProps: Readonly<ComponentProps<T>>) => boolean
    ): MemoExoticComponent<T>;

默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

默认使用

//浅比较
const MyComponent = React.memo(function MyComponent(props) {
  /* 使用 props 渲染 */
});

自定义比较函数

const Message = memo(
    function Message({value, changeValue}) {
        console.log('渲染了');
        //点击子组件的按钮,子组件的值value发生改变了,子组件会重新渲染
        return (
            <div>
                <button onClick={changeValue}>改变有关数据</button>
                <p>与Message渲染有关的数据{value}</p>
            </div>
        )
    },
    (prve,next)=>{
        //只要value值相等,就认为props值没有发生改变
        //这样就不会渲染,和useCallback实现的不重新渲染效果一样,但是原理不一样
        //不重新渲染,导致changeValue不会发生改变
        //value的值永远为执行了一次changeValue的值
        //不要这么用,还是useCallback(()=>{},[value])这样
       if(prve.value==next.value){
           return true;
       }
        
    }
)

React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useStateuseReduceruseContext的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。

此方法仅作为性能优化的方式而存在。但请不要依赖它来“阻止”渲染,因为这会产生 bug。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值