useState hook - 柏拉图区块链数据智能综合指南。 垂直搜索。 哎。

useState 钩子——综合指南

什么是状态?

在我们深入研究 useState 钩子之前,让我们先了解一下这个术语 州。

状态表示在给定时间点有关某事的信息。

例如,让我们考虑一个文本框。

最初,此文本框内没有任何内容,因此其状态为 空的. 假设您开始在其中输入 Hello,对于每一次击键,文本框的状态都会改变。 一开始会是“H”,然后是“He”,然后是“Hel”,依此类推,直到变成“Hello”。

另外,请注意,在您输入时,您不会丢失之前的值。 如果您按“H”,然后按“e”,您将得到“He”,而不仅仅是“e”。 换句话说,您可以将状态视为 记忆 的文本框。

React 组件中对状态的需求。

让我们借助一个例子来理解这一点。

Codesanbox 无状态

这里我们有一个 点击计数器 显示“增量”按钮被单击的次数的组件。

我们正在使用 局部变量“计数器” 保持点击次数。

每次我们点击增量按钮, 手柄点击 函数将被调用。 此函数会将计数器值增加 1,并将该值记录在控制台中。

继续,单击 CodeSandbox 预览中的增量按钮。

没啥事儿?

好吧,我们的逻辑似乎是正确的。 每次单击时控制台(CodeSandbox)中记录的值都会正确更新,但为什么此更新没有反映在 UI 中?

这是因为 React 的工作方式。

  • 对局部变量的更改不会触发重新渲染。
  • 在重新渲染期间,从头开始创建组件,即再次执行组件的函数(在本例中为 ClickCounter 函数)。 由于变量(例如,计数器)是函数的本地变量,因此它们以前的值会丢失。

那么我们如何让组件记住渲染之间的值呢?

是的,你没看错! 我们在 使用状态 钩。

useState 钩子

useState 钩子提供了保存状态和触发重新渲染的机制。

我们来看看它的用法。

import React, { useState } from "react";
const state = useState(initialValue);

// OR

const state = React.useState(initialValue);

useState 挂钩返回一个包含两项的数组:

  • A 状态变量 在重新渲染期间保留其值。 传递给 useState 的初始值在第一次渲染期间被分配给状态变量。
  • A 设置函数 更新状态变量并触发重新渲染。
const state = useState(0);
const data = state[0];
const setData = state[1];

运用 数组解构 ,我们可以将上述语句重构为一条语句,如下所示:

const [data, setData] = useState(0);

传递给 useState 的初始值仅在第一次渲染期间使用。 对于重新渲染,它被忽略。

带有 useState 的计数器

现在,让我们更新前面的计数器示例以包含 useState 挂钩。

  • 由于我们需要重新渲染之间的计数器值,让我们将其转换为状态。
const [counter, setCounter] = useState(0);
  • 在 handleClick 函数中调用 setCounter。
const handleClick = () => {
  setCounter(counter + 1);
  console.log(`%c Counter:${counter}`, "color:green");
};

setCounter 函数会将计数器值更新 1 并触发重新渲染。 当在重新渲染时调用组件的函数时,useState 返回的状态变量将具有更新的值。

使用更新的代码试用 CodeSandbox。 单击“增量”按钮并查看 useState 的神奇作用。

带有 useState 的 Codesanbox

您可以验证在重新渲染时,功能组件 点击计数器 通过查看控制台日志再次调用。 在组件开头添加的日志“ClickCounter start”将记录在每个渲染中。

第一次渲染

重新渲染

更新器功能

假设我们想在每次点击时将 counter 的值增加 4。

const handleClick = () => {
  setCounter(counter + 1);
    setCounter(counter + 1);
    setCounter(counter + 1);
    setCounter(counter + 1);
    console.log(`%c Counter:${counter}`, "color:green");
    };

假设 counter 的初始值为 0。单击按钮后,您希望看到什么?

不带更新功能

你预计计数是 4 对吗? 但是为什么你会看到 1 呢?

a) 每个渲染都与一个状态相关联。 该状态的值在该渲染的生命周期内保持锁定。

请注意,handleClick 函数内的日志将计数器值打印为 0。

无论您调用多少次 setCounter 方法,counter 的值都保持不变。

const handleClick = () => {
  setCounter(counter + 1);
    setCounter(counter + 1);
    setCounter(counter + 1);
    setCounter(counter + 1);
    console.log(`%c Counter:${counter}`, "color:green");
    };
b) 在事件处理程序中的所有代码都被执行之前,React 不会触发重新渲染。

因此,每个 setCounter 调用都不会触发单独的渲染。 相反,React 将这些 setter 函数添加到队列中。 按它们排队的顺序执行它们。 执行完所有语句后对状态所做的更新会反映在下一次渲染中。 这种多个状态更新的排队称为 配料. 它使 React 的性能更高。

因此,这里我们得到一个渲染而不是 4 个不同的渲染。

此示例很简单,您可以通过更新代码来解决此问题,如下所示:

const handleClick = () => {
setCounter(counter + 4);
    console.log(`%c Counter:${counter}`, "color:green");
    };

但是,如果您有一个用例,您想在下一次渲染之前多次更新状态怎么办。

那是_ 更新 _ 函数派上用场。

我们可以使用 updater 函数重构前面的示例,如下所示:

const handleClick = () => {
  setCounter(prevCounter => prevCounter + 1);
    setCounter(prevCounter => prevCounter + 1);
    setCounter(prevCounter => prevCounter + 1);
    setCounter(prevCounter => prevCounter + 1);
    console.log(`%c Counter:${counter}`, "color:green");
    };

这里 上一个计数器 ⇒ 上一个计数器 + 1 表示更新函数。

如前所述,这些更新程序语句也排队(批处理)。

updater 函数接收一个挂起/前一个状态,用于计算下一个状态。

更新函数批处理

下面是添加了更新程序功能的 CodeSandbox。 尝试单击增量按钮。

更新程序功能沙箱

初始化函数

看看下面的例子。 这里我们调用 getItems 函数来获取状态的初始值。

import React from "react";
import { useState } from "react";
function ListItems() { 
  const getItems = () => { 
    console.log(`%c getItems called`, "color:hotpink");
    	return Array(50).fill(0); 
    }; 
  const [items, setItems] = useState(getItems()); 
    
    return ( 
    	<div className="card">
        <ul> {items.map((item, index) => 
        	( <li key={index}>Item {index + 1}		</li>))} 
        </ul> <button onClick={() => setItems([...items, 0])}>Add Item</button> 	</div> );
} 
export default ListItems;

此函数创建一个大小为 50 的数组并用零填充该数组。 请参考下图。

用 50 个零填充的数组

然后这些项目显示在屏幕上。

一切似乎都很好,但我们这里有问题。

点击 新增项目 按钮(位于项目列表之后)将新项目添加到列表中。 观察日志。

没有初始化函数

你看到这里的问题了吗?

每次添加项目时,日志“getItems called”都会添加到控制台。 这意味着在每次渲染时都会调用此函数。

请记住,useState 在第一次渲染后忽略了传递给它的初始值,但这里的初始值仍在重新计算中。 如果我们正在创建大型数组或执行繁重的计算,这可能会很昂贵。

我们可以通过传递来解决这个问题 获取项目 作为一个_ 初始化程序 _ 功能。

现在,让我们对代码进行一些小改动。

const [items, setItems] = useState(getItems);

带初始化函数

请参阅 CodeSandbox 中的控制台窗口。 请注意,“getItems called”日志仅在第一次渲染时打印。 添加后续项目时,此日志不存在。

尽管这两个示例之间没有视觉上的差异,但就性能而言,它们是不同的。

请记住,当您需要初始状态的函数时,请始终传递该函数或在另一个函数中调用该函数。 切勿直接调用该函数。

✅ const [items, setItems] = useState(getItems);
✅ const [items, setItems] = useState(() => getItems());
❌ const [items, setItems] = useState(getItems());

我可以拥有多少个 useState 挂钩

您可以根据需要在组件中拥有尽可能多的 useState 挂钩。

查看 CodeSandbox

多个 useState 挂钩

下面的组件具有三种不同的状态——用户名、密码、keepMeSignedIn。

尝试更新用户名 keepMeSignedIn 的值。 单击登录按钮时,更新的状态会记录在控制台中。

亮点

  • useState 提供了一种机制来触发重新渲染并在重新渲染之间保持状态。
  • 需要时使用更新程序功能:
    • 根据前一个状态计算下一个状态。
    • 在下一次渲染之前对状态执行多次更新。
  • 如果初始状态是从函数获得的——使用初始化函数语法。
  • 一个组件内可以有多个 useState 钩子。

喜欢这篇文章吗? 与他人分享。
最初是为我的个人博客写的—— https://gauravsen.com/use-state-hook

时间戳记:

更多来自 Codementor 反应事实