React useCallback钩子的作用方法demo
Jovie 人气:0简介
如果你还不熟悉钩子的概念,请务必查看本文章,因为它对钩子的概念提供了一个非常好的、深入的概述,以及一些钩子的例子。
useCallback
钩子是用来缓存一个记忆化的回调函数,以节省任何重新计算的开销。
这个钩子可以阻止一个组件重新渲染,除非它的道具发生了变化,这意味着我们现在可以隔离资源密集型的函数,这样它们就不会在每个组件渲染时自动运行。
最好是展示一个有利于使用该钩子的场景,这样我们就能更好地理解我们为达成一个问题所采取的步骤,然后再解释使用useCallback
钩子背后的思考过程。
项目概述
我们将从搭建一个全新的React项目的脚手架开始。首先,我们将创建一个新的项目目录,之后,我们将使用终端初始化一个新项目。
在这个过程中,你可以使用npm、npx或者yarn。你要运行的命令是:
- npm。
npm init react-app app-name
- npx
: npx create-react-app app-name
- yarn。
yarn 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
组件时,都会打印到控制台。
意外的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
函数,而不是一个数组值。
如果我们要改变这一点,我们应该看到一个警告。
setState的值而不是回调作为参数
useCallback依赖数组的警告
总结
我希望你喜欢读这篇文章,并希望你对useCallback
钩子是什么,它的作用,以及什么时候应该使用它有了更好的理解。
加载全部内容