React缓存——useMemo()与useCallback

1,094 阅读4分钟

hooks缓存

​ react渲染页面是通过render把虚拟结点渲染到页面,通过对比fiber树,re-render重新渲染页面,为了更好的性能,我们一般提倡尽可能地减少react重新渲染页面的次数,这时候,usememo()和useCallback()就应运而生。

useMemo()

const cachedValue= useMemo(calculateValue,dependencies)

可以在react每次重新渲染的时候缓存计算的结果,其中:

  • calculateValue: 缓存计算值的函数,且要为没有任何参数的纯函数,可以返回任意类型。react只会在首次渲染的时候调用该函数,之后如果dependencies没有发生变化时,react就直接返回相同的数值,避免了重复调用该函数;如果dependencies变化,才再调用该函数。
  • dependencies: 依赖项,为calculateValue中所使用到的响应式变量组成的数组,包括props\state和所有直接在组件中定义的函数和变量。React 使用 Object.is 将每个依赖项与其之前的值进行比较。
Object.is()

Object.is()== 运算符并不等价。== 运算符在测试相等性之前,会对两个操作数进行类型转换(如果它们不是相同的类型),这可能会导致一些非预期的行为,例如 "" == false 的结果是 true,但是 Object.is() 不会对其操作数进行类型转换。

Object.is()等价于 === 运算符。Object.is()=== 之间的唯一区别在于它们处理带符号的 0 和 NaN 值的时候。=== 运算符(和 == 运算符)将数值 -0+0 视为相等,但是会将 NaN 视为彼此不相等。

注意:

和其他hooks一样,只能在组件的顶层和自定义hooks中使用,不能在循环或条件语句中调用。

用法
  1. 缓存组件,避免反复重新加载整个组件

    function Todolist({todos,tab}){
        const visibleTodos=filterTodos(todos,tab)
    }
    

    上面的组件需要利用filterTodos的方法过滤得到有效的todos,但由于利用到了props和state,所以每次更新都会重新运行filterTodos函数,因此,我们可以使用usememo进行改善:

    function Todolist({todos,tab}){
        const visibleTodos=useMemo(()=>filterTodos(todos,tab),[todos,tab])
    }
    
  2. 跳过组件的重新渲染

    在默认情况下,当一个组件重新渲染时,react会递归地重新渲染它的所有子组件。依旧使用上述的例子,假设将visibleTodos传递给todoList的子组件:

    function TodoList({todos,tab}){
    const visibleTodos=filterTodos(todos,tab)
    //依然是建议使用useMemo()
      const visibleTodos=useMemo(()=>filterTodos(todos,tab),[todos,tab])
     return(
     <div>
         <List items={visibleTodos} />
     </div>
     )
    }
    

    注意:对于一个直接依赖于在组件中创建的对象,需要对这个对象本身进行memo包裹

    例如:这里的NotedTodos是在组件中创建的对象,当组件重新渲染时,组件中所有的代码都会再次运行,所以创建NotedTodos的代码也会在每次重新渲染时运行,而每次重新渲染的NotedTodos不一样,所以这里即便使用了useMemo, react也会重新渲染。

    function TodoList({todos,tab,ischecked}){
    const NotedTodos={noted:ischecked}
    
    const visibleTodos=useMemo(()=>{
        return filterTodos(todos,tab,NotedTodos)
    },[todos,tab,NotedTodos])
    }
    

    解决方式,使用memo包裹NotedTodos及使用了其作为依赖项的变量:

    function TodoList({todos,tab,ischecked}){
    const visibleTodos = useMemo(()=>{
        const NotedTodos={noted:ischecked}
        return filterTodos(todos,tab,NotedTodos)
    },[todos,tab,ischecked])
    

useCallback( )

简单来说,可以看作是useMemo的一个语法糖,适用于缓存函数:

const visibleTodos=useMemo(()=>{
    return ()=>{
        todos.find(item=>item===tabs)
    }
},[todos,tab])
}

//相当于
const visibleTodos=useCallback(()=>{
    todos.find(item=>item===tabs)
} ,[todos,tab])
}

// 在 React 内部的简化实现
function useCallback(fn, dependencies) {
  return useMemo(() => fn, dependencies);
}
  • 二者区别:

    • useMemo 缓存函数调用的结果。在这里,它缓存了调用 computeRequirements(product) 的结果。除非 product 发生改变,否则它将不会发生变化。这让你向下传递 requirements 时而无需不必要地重新渲染 ShippingForm。必要时,React 将会调用传入的函数重新计算结果。(调用)
    • useCallback 缓存函数本身。不像 useMemo,它不会调用你传入的函数。相反,它缓存此函数。从而除非 productIdreferrer 发生改变,handleSubmit 自己将不会发生改变。这让你向下传递 handleSubmit 函数而无需不必要地重新渲染 ShippingForm。直至用户提交表单,你的代码都将不会运行。(创建)
使用场景
  1. usememo返回函数
  2. 优化自定义hooks