垃圾站 生活小记 React useCallback钩子的作用方法demo

React useCallback钩子的作用方法demo

如果你还不熟悉钩子的概念,请务必查看本文章,因为它对钩子的概念提供了一个非常好的、深入的概述,以及一些钩子的例子。

useCallback 钩子是用来缓存一个记忆化的回调函数,以节省任何重新计算的开销。

这个钩子可以阻止一个组件重新渲染,除非它的道具发生了变化,这意味着我们现在可以隔离资源密集型的函数,这样它们就不会在每个组件渲染时自动运行。

最好是展示一个有利于使用该钩子的场景,这样我们就能更好地理解我们为达成一个问题所采取的步骤,然后再解释使用useCallback 钩子背后的思考过程。

项目概述

我们将从搭建一个全新的React项目的脚手架开始。首先,我们将创建一个新的项目目录,之后,我们将使用终端初始化一个新项目。

在这个过程中,你可以使用npmnpx或者yarn。你要运行的命令是:

  • npmnpm init react-app app-name
  • npx: npx create-react-app app-name
  • yarnyarn create react-app app-name

现在我们已经设置好了一切,让我们直接进入有趣的部分。

项目进展

由于这是一个小项目,我们将把所有的代码放在根src 目录下的App.js 文件内,它看起来会是这样的:

import { useState, memo } from "react";
import './App.css';
const Todos = ({ todos, addTodo }) => {
  console.log("child render");
  return (
<div className="todos-container">
  <h2>My Todos</h2>
  <div className="todos">
{todos.map((todo, index) => {
  return <p key={index}>{todo}</p>;
})}
  </div>
  <button onClick={addTodo}>Add Todo</button>
</div>
  );
};
const MemoizedTodos = memo(Todos);
const App = () => {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);
  const increment = () => {
setCount((c) => c + 1);
  };
  const addTodo = () => {
setTodos((t) => [...t, "New Todo"]);
  };
  return (
<div className="App">
  <MemoizedTodos
todos={todos}
addTodo={addTodo}
  />
  <hr />
  <div className="counter-container">
<p>Count: {count}</p>
<button onClick={increment}>+</button>
  </div>
</div>
  );
};
export default App;

这里有相当多的东西需要解压,所以让我们看看我们有什么。

首先,我们定义了一个Todos 组件,它的道具是一个todos列表,以及一个函数,一旦被调用,就会添加一个新的todo。这个组件的任务是将todos渲染到屏幕上,并在按下Add Todo 按钮时添加一个新的todo。

然后,我们有其余的App 组件,除了渲染和传递道具给MemoizedTodos 组件外,还在屏幕上显示一个可以递增的计数器。

而为了让一切看起来更好一点,我们还将在我们的App.css 文件中添加以下样式:

.App {
  text-align: left;
  width: 80vw;
  margin: 5vh auto 0 auto;
}

那么,问题出在哪里?

现在,你可能会问自己:”这个应用程序有什么问题?”;的确,一切似乎都在正常工作。然而,问题是,每次我们点击+ 按钮来增加计数器时,Todos 组件就会被重新渲染。

我们可以通过检查控制台来检查,每次我们重新渲染Todos 组件时,都会打印到控制台。

React useCallback钩子的作用方法demo插图

意外的Todos 组件的重新渲染

问题的根本原因

你看,在App 组件中,我们定义了addTodo 函数,它将在每次App 组件重新渲染时被重新创建。当App 组件的状态发生变化时,它就会重新渲染,其中包括todos和计数器

我们正在使用[memo](https://reactjs.org/docs/react-api.html#reactmemo),所以Todos 组件不应该重新渲染,因为当计数增加时,todos 的状态和addTodo 的功能都没有改变。

因此,基本上,问题在于addTodo 是定义在App 组件内的,并且在该组件重新生成时被重新创建。

解决方法

实际上,我们有两种方法可以解决这个问题:

  • 通过在App 组件之外定义addTodo 函数。
  • 通过对addTodo 函数的记忆化

虽然第一种方法似乎是明确的解决方案,但我们不能总是依靠将一个函数排除在组件的范围之外,而使其成为一个纯函数。

对函数进行记忆

然后我们再来看看useCallback 钩子,它正是通过对函数进行备忘来解决我们现在面临的这个问题,以避免其重构。

我们所要做的就是从React中导入useCallback 钩子,并将调用钩子的结果与钩子的回调返回的状态更新分配给addTodo 函数,就这样:

const addTodo = useCallback(() => {
  setTodos((t) => [...t, "New Todo"]);
}, []);

而这个轻微的调整应该可以解决我们之前的问题。

现在,如果你想知道为什么我们没有把todos 数组添加到钩子的依赖数组中,那是因为React会拾取状态,因为我们使用的是以回调为参数的setTodos 函数,而不是一个数组值。

如果我们要改变这一点,我们应该看到一个警告。

React useCallback钩子的作用方法demo插图1

setState的值而不是回调作为参数

React useCallback钩子的作用方法demo插图2

useCallback依赖数组的警告

总结

我希望你喜欢读这篇文章,并希望你对useCallback 钩子是什么,它的作用,以及什么时候应该使用它有了更好的理解。

上一篇
下一篇
联系我们

联系我们

返回顶部