超性感的React Hooks(十一)useCallback、useMemo

简介: 这个时候,我们思考一个问题,当我们重复调用summation(100)时,函数内部的循环计算是不是有点冗余?因为传入的参数一样,得到的结果必定也是一样,因此如果传入的参数一致,是不是可以不用再重复计算直接用上次的计算结果返回呢?当然可以,利用闭包能够实现我们的目的。

在实践开发中,有一种优化手段叫做记忆函数


什么是记忆函数?用一个例子来说明。


我们想要计算从1到某个整数的总和。封装一个方法来实现这个目的。


function summation(target: number) {
  let sum = 0;
  for(let i = 1; i <= target; i++) {
    sum += i;
  }
  return sum;
}


验证一下结果,没有问题。


微信图片_20220510201823.png


这个时候,我们思考一个问题,当我们重复调用summation(100)时,函数内部的循环计算是不是有点冗余?因为传入的参数一样,得到的结果必定也是一样,因此如果传入的参数一致,是不是可以不用再重复计算直接用上次的计算结果返回呢?


当然可以,利用闭包能够实现我们的目的。


// 初始化一个非正常数字,用于缓存上一次的计算结果
let preTarget = -1;
let memoSum = 0;
export function memoSummation(target: number) {
  // 如果传入的参数与上一次一样,直接换回缓存结果
  if (preTarget > 0 && preTarget === target) {
    return memoSum;
  }
  console.log('我出现,就表示重新计算了一次');
  // 缓存本次传入的参数
  preTarget = target;
  let sum = 0;
  for (let i = 1; i <= target; i++) {
    sum += i;
  }
  // 缓存本次的计算结果
  memoSum = sum;
  return sum;
}


多次调用memoSummation(1000),没有问题,我们的目的达到了。后两次的调用直接返回了记忆中的结果。


微信图片_20220510201819.jpg


这就是记忆函数。记忆函数利用闭包,在确保返回结果一定正确的情况下,减少了重复冗余的计算过程。这是我们试图利用记忆函数去优化我们代码的目的所在。


1


react hooks提供的api,大多都有记忆功能。例如


useStateuseEffect


useLayoutEffect


useReducer


useRefuseMemo 记忆计算结果


useCallback 记忆函数体


其他几个api的使用方法,我们在前面已经一一跟大家分析过。这里主要关注useMemo与useCallback。


useMemo


useMemo缓存计算结果。它接收两个参数,第一个参数为计算过程(回调函数,必须返回一个结果),第二个参数是依赖项(数组),当依赖项中某一个发生变化,结果将会重新计算。


// allow undefined, but don't make it optional as that is very likely a mistakefunction useMemo<T>(fact
ory: () => T, deps: DependencyList | undefined): T;


useCallback


useCallback的使用几乎与useMemo一样,不过useCallback缓存的是一个函数体,当依赖项中的一项发现变化,函数体会重新创建。



function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;


写一个案例,来观察一下他们的使用。


import React, { useMemo, useState, useCallback } from 'react';
import { Button } from 'antd-mobile';
export default function App() {
  const [target, setTarget] = useState(0);
  const [other, setOther] = useState(0)
  const sum = useMemo(() => {
    console.log('重新计算一次');
    let _sum = 0;
    for (let i = 1; i <= target; i++) {
      _sum += i;
    }
    return _sum;
  }, [target]);
  const inputChange = useCallback((e) => {
    console.log(e.target.value);
  }, []);
  return (
    <div style={{ width: '200px', margin: 'auto' }}>
      <input type="text" onChange={inputChange} />
      <div style={{ width: '80px', margin: '100px auto', fontSize: '40px' }}>{target} {sum}</div>
      <Button onClick={() => setTarget(target + 1)}>递增</Button>
      <Button onClick={() => setTarget(target - 1)}>递减</Button>
      <div style={{ width: '80px', margin: '100px auto', fontSize: '20px' }}>干扰项 {other}</div>
      <Button onClick={() => setOther(other + 1)}>递增</Button>
      <Button onClick={() => setOther(other - 1)}>递减</Button>
    </div>
  )
}


2


useMemo/useCallback的使用非常简单,不过我们需要思考一个问题,使用他们一定能够达到优化的目的吗?


React的学习经常容易陷入过度优化的误区。一些人在得知shouldComponentUpdate能够优化性能,恨不得每个组件都要用一下,不用就感觉自己的组件有问题。useMemo/useCallback也是一样。


明白了记忆函数的原理,我们应该知道,记忆函数并非完全没有代价,我们需要创建闭包,占用更多的内存,用以解决计算上的冗余


useMemo/useCallback也是一样,这是一种成本上的交换。那么我们在使用时,就必须要思考,这样的交换,到底值不值?


如果不使用useCallback,我们就必须在函数组件内部创建超多的函数,这种情况是不是就一定有性能问题呢?

不是的。


我们知道,一个函数执行完毕之后,就会从函数调用栈中被弹出,里面的内存也会被回收。因此,即使在函数内部创建了多个函数,执行完毕之后,这些创建的函数也都会被释放掉。函数式组件的性能是非常快的。相比class,函数更轻量,也避免了使用高阶组件、renderProps等会造成额外层级的技术。使用合理的情况下,性能几乎不会有什么问题。


而当我们使用useMemo/useCallback时,由于新增了对于闭包的使用,新增了对于依赖项的比较逻辑,因此,盲目使用它们,甚至可能会让你的组件变得更慢。


大多数情况下,这样的交换,并不划算,或者赚得不多。你的组件可能并不需要使用useMemo/useCallback来优化。


3


那么,什么时候使用useMemo/useCallback比较合适?


总的原则,就是当你认为,交换能够赚的时候去使用它们。


例如在一个一定会多次re-render的组件里,input的回调没有任何依赖项,我们就可以使用useCallback来降低多次执行带来的重复创建同样方法的负担。


即使这样,也可能并不会优化多少,因为我们缓存的函数体本身就非常简单,不会造成太大的负担


<input type="text" onChange={inputChange} />
const inputChange = useCallback((e) => {
  setValue(e.target.value);
}, []);


但是,同样的场景,如果该组件一定只会渲染一次,那么使用useCallback就完全没有必要。


通常情况下,当函数体或者结果的计算过程非常复杂时,我们才会考虑优先使用useCallback/useMemo。


例如,在日历组件中,需要根据今天的日期,计算出当月的所有天数以及相关的信息。

不过,当依赖项会频繁变动时,我们也要考虑使用useMemo/useCallback是否划算


每当依赖项变动,useMemo/useCallback不会直接返回计算结果,这个时候,结果会重新计算,函数体会重新创建。因此依赖项变动频繁时,需要慎重考虑。


最后,一图总结全文。


微信图片_20220510201815.jpg

相关文章
|
4天前
|
设计模式 存储 前端开发
React开发设计模式及原则概念问题之自定义Hooks的作用是什么,自定义Hooks设计时要遵循什么原则呢
React开发设计模式及原则概念问题之自定义Hooks的作用是什么,自定义Hooks设计时要遵循什么原则呢
|
1月前
|
前端开发 JavaScript 安全
TypeScript在React Hooks中的应用:提升React开发的类型安全与可维护性
【7月更文挑战第17天】TypeScript在React Hooks中的应用极大地提升了React应用的类型安全性和可维护性。通过为状态、依赖项和自定义Hooks指定明确的类型,开发者可以编写更加健壮、易于理解和维护的代码。随着React和TypeScript的不断发展,结合两者的优势将成为构建现代Web应用的标准做法。
|
13天前
|
前端开发 开发者
彻底颠覆!React Hooks带来前端开发的革命,你准备好了吗?
【8月更文挑战第6天】在现代Web开发中,React作为顶级前端框架,以高效性能和丰富生态著称。React Hooks自16.8版本引入,赋予函数组件使用状态和生命周期的能力,使代码更简洁、模块化,易于维护。常用Hooks如`useState`、`useEffect`等简化了状态管理和副作用操作。Hooks不仅增强了组件能力,提高了代码可读性和可维护性,还在各种应用场景中展现出强大功能,尤其是在中大型项目中优化了代码结构和提升了开发效率。总之,React Hooks为前端开发注入了新活力,推动了更高效便捷的开发实践。
26 1
|
1月前
|
前端开发
React Hooks实战技巧:提升你的组件开发效率
【7月更文挑战第16天】React Hooks为React开发带来了革命性的变化,使得函数式组件更加强大和灵活。通过掌握上述实战技巧,你可以更高效地编写清晰、可维护和可复用的React组件。希望这些技巧能帮助你在React开发之路上走得更远。
|
10天前
|
JavaScript 前端开发 API
浅谈:为啥 Vue 和 React 都选择了 Hooks?
浅谈:为啥 Vue 和 React 都选择了 Hooks?
|
1月前
|
前端开发 JavaScript 开发者
前端框架与库 - React生命周期与Hooks
【7月更文挑战第13天】React 框架革新UI构建,引入Hooks简化组件状态管理和副作用处理。组件生命周期涉及挂载、更新、卸载,对应不同方法,如`componentDidMount`、`shouldComponentUpdate`等,但现在推荐使用`useState`和`useEffect` Hooks。`useEffect`处理副作用,需注意清理和依赖数组。避免问题的关键在于正确使用Hooks和理解其工作模式,以构建高效应用。
|
1月前
|
存储 前端开发 JavaScript
react hooks 学习进阶
【7月更文挑战第12天】 React Hooks(自16.8版起)让函数组件能处理状态和副作用。useState用于添加状态管理,useEffect处理副作用,useContext共享数据,useReducer处理复杂状态逻辑,useRef获取引用。进阶技巧涉及性能优化,如useMemo和useCallback,以及遵循规则避免在不适当位置调用Hooks。理解异步更新机制和结合Redux等库提升应用复杂性管理。持续学习新技巧是关键。
30 0
|
7天前
|
前端开发 JavaScript UED
React 基础与实践 | 青训营笔记
React 基础与实践 | 青训营笔记
12 0
|
1月前
|
前端开发 JavaScript Java
React 速通笔记
【7月更文挑战第17天】
28 1
|
前端开发
前端学习笔记202305学习笔记第二十九天-React keep alive原理之2
前端学习笔记202305学习笔记第二十九天-React keep alive原理之2
59 0