usecallback()与usememo()

简单的说

都是用来监听数据变化 来进行控制渲染、减少不必要的渲染 、优化性能
usecallback()是用来监听数据变化从而调用方法
usememo()是用来监听数据变化从而改变数据 使用return返回变化的数据 当然return 也可以返回方法 所以usememo()可以代替usecallback()

下面详解

useCallback:缓存回调函数

在 React 函数组件中,每一次 UI 的变化,都是通过重新执行整个函数来完成的,这和传统的 Class 组件有很大区别:函数组件中并没有一个直接的方式在多次渲染之间维持一个状态。 比如下面的代码中,我们在加号按钮上定义了一个事件处理函数,用来让计数器加 1。但是因为定义是在函数组件内部,因此在多次渲染之间,是无法重用 handleIncrement 这个函数的,而是每次都需要创建一个新的:

function Counter() {
  const [count, setCount] = useState(0);
  const handleIncrement = () => setCount(count + 1);
  // ...
  return <button onClick={handleIncrement}>+</button>
}

你不妨思考下这个过程。每次组件状态发生变化的时候,函数组件实际上都会重新执行一遍。在每次执行的时候,实际上都会创建一个新的事件处理函数 handleIncrement。
这个事件处理函数中呢,包含了 count 这个变量的闭包,以确保每次能够得到正确的结果。 这也意味着,即使 count 没有发生变化,但是函数组件因为其它状态发生变化而重新渲染时,这种写法也会每次创建一个新的函数
创建一个新的事件处理函数,虽然不影响结果的正确性,但其实是没必要的。因为这样做不仅增加了系统的开销,更重要的是:每次创建新函数的方式会让接收事件处理函数的组件,需要重新渲染。
比如这个例子中的 button 组件,接收了 handleIncrement ,并作为一个属性。如果每次都是一个新的,那么这个 React 就会认为这个组件的 props 发生了变化,从而必须重新渲染。因此,我们需要做到的是:**只有当 count 发生变化时,我们才需要重新定一个回调函数。**而这正是 useCallback 这个 Hook 的作用。

import React, { useState, useCallback } from 'react';
function Counter() {
  const [count, setCount] = useState(0);
  const handleIncrement = useCallback(
    () => setCount(count + 1),
    [count], // 只有当 count 发生变化时,才会重新创建回调函数
  );
  // ...
  return <button onClick={handleIncrement}>+</button>
}

在这里,我们把 count 这个 state ,作为一个依赖传递给 useCallback。这样,只有 count 发生变化的时候,才需要重新创建一个回调函数,这样就保证了组件不会创建重复的回调函数。而接收这个回调函数作为属性的组件,也不会频繁地需要重新渲染。

useMemo:缓存计算的结果

如果某个数据是通过其它数据计算得到的,那么只有当用到的数据,也就是依赖的数据发生变化的时候,才应该需要重新计算。
举个例子,对于一个显示用户信息的列表,现在需要对用户名进行搜索,且 UI 上需要根据搜索关键字显示过滤后的用户,那么这样一个功能需要有两个状态:
1.用户列表数据本身:来自某个请求。
2.搜索关键字:用户在搜索框输入的数据。
无论是两个数据中的哪一个发生变化,都需要过滤用户列表以获得需要展示的数据。那么如果不使用 useMemo 的话,就需要用这样的代码实现:

import React, { useState, useEffect } from "react";

export default function SearchUserList() {
  const [users, setUsers] = useState(null);
  const [searchKey, setSearchKey] = useState("");

  useEffect(() => {
    const doFetch = async () => {
      // 组件首次加载时发请求获取用户数据
      const res = await fetch("https://reqres.in/api/users/");
      setUsers(await res.json());
    };
    doFetch();
  }, []);
  let usersToShow = null;

  if (users) {
    // 无论组件为何刷新,这里一定会对数组做一次过滤的操作
    usersToShow = users.data.filter((user) =>
      user.first_name.includes(searchKey),
    );
  }

  return (
    <div>
      <input
        type="text"
        value={searchKey}
        onChange={(evt) => setSearchKey(evt.target.value)}
      />
      <ul>
        {usersToShow &&
          usersToShow.length > 0 &&
          usersToShow.map((user) => {
            return <li key={user.id}>{user.first_name}</li>;
          })}
      </ul>
    </div>
  );
}

在这个例子中,无论组件为何要进行一次重新渲染,实际上都需要进行一次过滤的操作。但其实你只需要在 users 或者 searchKey 这两个状态中的某一个发生变化时,重新计算获得需要展示的数据就行了。那么,这个时候,我们就可以用 useMemo 这个 Hook 来实现这个逻辑,缓存计算的结果
//…
// 使用 userMemo 缓存计算的结果

const usersToShow = useMemo(() => {
    if (!users) return null;
    return users.data.filter((user) => {
      return user.first_name.includes(searchKey));
    }
  }, [users, searchKey]);
//...

可以看到,通过 useMemo 这个 Hook,可以避免在用到的数据没发生变化时进行的重复计算。虽然例子展示的是一个很简单的场景,但如果是一个复杂的计算,那么对于提升性能会有很大的帮助。
这也是 userMemo 的一大好处:避免重复计算。 除了避免重复计算之外,useMemo 还有一个很重要的好处:避免子组件的重复渲染。比如在例子中的 usersToShow 这个变量,如果每次都需要重新计算来得到,那么对于 UserList 这个组件而言,就会每次都需要刷新,因为它将 usersToShow 作为了一个属性。而一旦能够缓存上次的结果,就和 useCallback 的场景一样,可以避免很多不必要的组件刷新。

这个时候,如果我们结合 useMemo 和 useCallback 这两个 Hooks 一起看,会发现一个有趣的特性,那就是 useCallback 的功能其实是可以用 useMemo 来实现的。比如下面的代码就是利用 useMemo 实现了 useCallback 的功能

 const myEventHandler = useMemo(() => {
   // 返回一个函数作为缓存结果
   return () => {
     // 在这里进行事件处理
   }
 }, [dep1, dep2]);

理解了这一点,相信你一下子会对这两个 Hooks 的机制有更进一步的认识,也就不用死记硬背两个 API 都是干嘛的了,因为从本质上来说,它们只是做了同一件事情:建立了一个绑定某个结果到依赖数据的关系。只有当依赖变了,这个结果才需要被重新得到。

深入讨论usecallback()

开发过程中我遇到了

const toggleTheme = useCallback(() => 
{ 
setTheme((theme) =>
 (theme === "light" ? "dark" : "light")); 
}, []);

疑问?为什么没有依赖项 不需要依赖theme 获取最新的theme的值么
答:
useCallback 的依赖项数组是用于确定何时重新计算缓存的函数的。如果依赖项数组中有任何值发生变化,那么函数将被重新创建。

然而,对于 toggleTheme 函数,你并不需要在依赖项数组中包含 theme。这是因为你是在函数内部直接读取 theme 的最新值,而不是依赖于外部的 theme 变量。这是 setTheme 函数的一个特性:当你传递一个函数给 setTheme,React 会将 当前的 theme 状态值作为那个函数的参数

所以,即使 theme 值在多次渲染之间发生变化,toggleTheme 函数总是能读取到最新的 theme 值,因此不需要在依赖项数组中包含 theme。

这种模式在你想要根据当前状态值更新状态时是非常有用的,因为它可以确保你总是使用最新的状态值,而不是在闭包中缓存的旧值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

临夏_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值