jtwang7 / React-Note

React 学习笔记
8 stars 2 forks source link

React - Hooks (useMemo & useCallback) #7

Open jtwang7 opened 3 years ago

jtwang7 commented 3 years ago

React - Hooks (useMemo & useCallback)

参考文章:

前言

Hooks 中,对于状态的持久性保存是非常常见的。由于函数组件其本身就充当了 render() 函数的功能,因此每次组件重新渲染时,都会执行函数组件,导致函数组件内部定义的变量和方法都会被重新创建一遍。

export default function SubCounter(props){
    let a = 2;
    let b = function () {...}
    return (
        <button onClick={b}>{a}</button>
    )
}

如上所示, 发生 click 事件后,若 b 方法中更新了 state,则会触发函数组件的重新渲染。此时变量 a 以及方法 b 都会被重新定义,这就导致 <button> 子组件接收了一个新的 b 方法(b 方法内容没变,但内存地址变了),进而导致子组件也发生了重新渲染。

memoized

为了让变量能够被保存,有以下几种办法:

闭包

在函数组件外部定义的变量或方法不会被重新定义,因为组件重新渲染时,仅重新执行函数组件而不是整个 JS 脚本。

在函数组件外部定义的变量或方法,会被函数组件内部调用并持有,因此形成了闭包。

let a = 2;
let b = function () {...}
export default function SubCounter(props){
return (
// 子组件不会被重新渲染
<button onClick={b}>{a}</button>
)
}

useMemo

useMemo 接收一个函数和依赖项数组,并返回一个 memoized 值。该值能持久保存,它仅会在某个依赖项改变时才被重新计算。该优化有助于避免在每次渲染时都进行高开销的计算。

传入 useMemo 的函数会在渲染期间执行,即在组件重新渲染的过程中(render 阶段)触发。

export default function SubCounter(props){
let a = 2;
// 变量 b 在每次重新渲染时持有历史状态,仅当 m 或 n 发生改变时才重新计算。
let b = useMemo(()=>computeExpensiveValue(m, n), [m, n])
return (
<button onClick={b}>{a}</button>
)
}

依赖项数组的特殊情况

  • 依赖项数组为 [],则只会在组件初次创建时执行一次,后续重新渲染均不会执行。
  • 不写依赖项数组,则默认任何值改变都会执行。

useCallback

useCallback 接收一个内联回调函数参数和一个依赖项数组(子组件依赖父组件的状态,即子组件会使用到父组件的值) ,useCallback 会返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

总结

闭包、useMemo、useCallback 目的都是为了将变量 memoized 化,优化性能,减少每次组件渲染都要重新创建变量或函数的问题。

当然,我们不需要将所有方法都 memoized 化,正常情况下简单的函数直接写在函数组件内部即可,因为每次重新定义该函数方法开销也不大。如果是复杂的函数,可以考虑将其提取到外部或者采用 useCallback 或 useMemo 方法。

jtwang7 commented 3 years ago

useCallback 回调注册

useCallback 同步回调

import { useCallback } from 'react';

const funcName = useCallback(
    ( ...args ) => { do something sync... }
, [ dependency list ])

useCallback async 异步回调

import { useCallback } from 'react';

const funcName = useCallback(
    async ( ...args ) => { do something sync... }
, [ dependency list ])

useCallback 的 async 异步回调注册,与同步注册一样,用 useCallback 将异步 async 函数包裹,并添加依赖项即可。