热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

简单易懂的ReactuseState()Hook指南

来源|https:github.comqq449245884xiaozhiissues147原文:https:dmitripavlutin.comreact-uses

来源 | https://github.com/qq449245884/xiaozhi/issues/147

原文:https://dmitripavlutin.com/react-usestate-hook-guide/

状态是隐藏在组件中的信息,组件可以在父组件不知道的情况下修改其状态。我更偏爱函数组件,因为它们足够简单,要使函数组件具有状态管理,可以useState() Hook。

本文会逐步讲解如何使用useState() Hook。此外,还会介绍一些常见useState() 坑。

1.使用 useState() 进行状态管理


无状态的函数组件没有状态,如下所示(部分代码):

import React from 'react';
function Bulbs() { return

;}

可以找 codesandbox 尝试一下。

运行效果:

这时,要如何添加一个按钮来打开/关闭灯泡呢?为此,咱们需要具有状态的函数组件,也就是有状态函数组件。

useState()是实现灯泡开关状态的 Hoook,将状态添加到函数组件需要4个步骤:启用状态、初始化、读取和更新。

1.1 启用状态

要将 转换为有状态组件,需要告诉 React:从'react'包中导入useState钩子,然后在组件函数的顶部调用useState()

大致如下所示:

import React, { useState } from 'react';function Bulbs() { ... = useState(...); return

;}

Bulbs函数的第一行调用useState()(暂时不要考Hook的参数和返回值)。重要的是,在组件内部调用 Hook 会使该函数成为有状态的函数组件。

启用状态后,下一步是初始化它。

1.2初始化状态

始时,灯泡关闭,对应到状态应使用false初始化 Hook:

import React, { useState } from 'react';
function Bulbs() { ... = useState(false); return

;}

useState(false)false初始化状态。

启用和初始化状态之后,如何读取它?来看看useState(false)返回什么。

1.3 读取状态

当 hook useState(initialState)被调用时,它返回一个数组,该数组的第一项是状态值

const stateArray = useState(false);stateArray[0]; // => 状态值

咱们读取组件的状态

function Bulbs() { const stateArray = useState(false); return

;}

组件状态初始化为false,可以打开 codesandbox 看看效果。

useState(false)返回一个数组,第一项包含状态值,该值当前为false(因为状态已用false初始化)。

咱们可以使用数组解构来将状态值提取到变量on上:

import React, { useState } from 'react';
function Bulbs() { const [on] = useState(false); return

;}

on状态变量保存状态值。

状态已经启用并初始化,现在可以读取它了。但是如何更新呢?再来看看useState(initialState)返回什么。

1.4 更新状态

用值更新状态

咱们已经知道,useState(initialState)返回一个数组,其中第一项是状态值,第二项是一个更新状态的函数。

const [state, setState] = useState(initialState);// 将状态更改为 'newState' 并触发重新渲染setState(newState);// 重新渲染`state`后的值为`newState`

要更新组件的状态,请使用新状态调用更新器函数setState(newState)。组件重新渲染后,状态接收新值newState

当点击开灯按钮时将灯泡开关状态更新为true,点击关灯时更新为 false

import React, { useState } from &#39;react&#39;;function Bulbs() {  const [on, setOn] &#61; useState(false); const lightOn &#61; () &#61;> setOn(true);  const lightOff &#61; () &#61;> setOn(false); return ( <>

);}

打开 codesandbox 自行尝试一下。

单击开灯按钮时&#xff0c;lightOn()函数将on更新为truesetOn(true)。单击关灯时也会发生相同的情况&#xff0c;只是状态更新为false

状态一旦改变&#xff0c;React 就会重新渲染组件&#xff0c;on变量获取新的状态值。

状态更新作为对提供一些新信息的事件的响应。这些事件包括按钮单击、HTTP 请求完成等&#xff0c;确保在事件回调或其他 Hook 回调中调用状态更新函数。

使用回调更新状态

当使用前一个状态计算新状态时&#xff0c;可以使用回调更新该状态:

const [state, setState] &#61; useState(initialState);...setState(prevState &#61;> nextState);
...

下面是一些事例&#xff1a;

// Toggle a booleanconst [toggled, setToggled] &#61; useState(false);setToggled(toggled &#61;> !toggled);// Increase a counterconst [count, setCount] &#61; useState(0);setCount(count &#61;> count &#43; 1);// Add an item to arrayconst [items, setItems] &#61; useState([]);setItems(items &#61;> [...items, &#39;New Item&#39;]);

接着&#xff0c;通过这种方式重新实现上面电灯的示例&#xff1a;

import React, { useState } from &#39;react&#39;;
function Bulbs() { const [on, setOn] &#61; useState(false);
const lightSwitch &#61; () &#61;> setOn(on &#61;> !on);
return ( <>

);}

打开 codesandbox 自行尝试一下。

setOn(on &#61;> !on)使用函数更新状态。

1.5 小结一波

  • 调用useState() Hook 来启用函数组件中的状态。

  • useState(initialValue)的第一个参数initialValue是状态的初始值。

  • [state, setState] &#61; useState(initialValue)返回一个包含2个元素的数组:状态值和状态更新函数。

  • 使用新值调用状态更新器函数setState(newState)更新状态。或者&#xff0c;可以使用一个回调setState(prev &#61;> next)来调用状态更新器&#xff0c;该回调将返回基于先前状态的新状态。

  • 调用状态更新器后&#xff0c;React 确保重新渲染组件&#xff0c;以使新状态变为当前状态。

2. 多种状态

通过多次调用useState()&#xff0c;一个函数组件可以拥有多个状态。

function MyComponent() { const [state1, setState1] &#61; useState(initial1); const [state2, setState2] &#61; useState(initial2); const [state3, setState3] &#61; useState(initial3); // ...}

需要注意的&#xff0c;要确保对useState()的多次调用在渲染之间始终保持相同的顺序(后面会讲)。

我们添加一个按钮添加灯泡&#xff0c;并添加一个新状态来保存灯泡数量&#xff0c;单击该按钮时&#xff0c;将添加一个新灯泡。

新的状态count 包含灯泡的数量&#xff0c;初始值为1&#xff1a;

import React, { useState } from &#39;react&#39;;function Bulbs() { const [on, setOn] &#61; useState(false);  const [count, setCount] &#61; useState(1); const lightSwitch &#61; () &#61;> setOn(on &#61;> !on);  const addBulbs &#61; () &#61;> setCount(count &#61;> count &#43; 1); const bulb &#61;

;  const bulbs &#61; Array(count).fill(bulb); return ( <>
{bulbs}
);}

打开演示&#xff0c;然后单击添加灯泡按钮&#xff1a;灯泡数量增加&#xff0c;单击开/关按钮可打开/关闭灯泡。

[on, setOn] &#61; useState(false) 管理开/关状态

  • [count, setCount] &#61; useState(1)管理灯泡数量。

多个状态可以在一个组件中正确工作。

3.状态的延迟初始化

每当 React 重新渲染组件时&#xff0c;都会执行useState(initialState)。如果初始状态是原始值&#xff08;数字&#xff0c;布尔值等&#xff09;&#xff0c;则不会有性能问题。

当初始状态需要昂贵的性能方面的操作时&#xff0c;可以通过为useState(computeInitialState)提供一个函数来使用状态的延迟初始化&#xff0c;如下所示&#xff1a;

function MyComponent({ bigJsonData }) { const [value, setValue] &#61; useState(function getInitialState() { const object &#61; JSON.parse(bigJsonData); // expensive operation return object.initialValue;  }); // ...}

getInitialState()仅在初始渲染时执行一次&#xff0c;以获得初始状态。在以后的组件渲染中&#xff0c;不会再调用getInitialState()&#xff0c;从而跳过昂贵的操作。

4. useState() 中的坑

现在咱们基本已经初步掌握了如何使用useState()&#xff0c;尽管如此&#xff0c;咱们必须注意在使用useState()时可能遇到的常见问题。

4.1 在哪里调用 useState()

在使用useState() Hook 时&#xff0c;必须遵循 Hook 的规则

  1. 仅顶层调用 Hook &#xff1a;不能在循环&#xff0c;条件&#xff0c;嵌套函数等中调用useState()。在多个useState()调用中&#xff0c;渲染之间的调用顺序必须相同。

  2. 仅从React 函数调用 Hook:必须仅在函数组件或自定义钩子内部调用useState()

来看看useState()的正确用法和错误用法的例子。

有效调用useState()

useState()在函数组件的顶层被正确调用

function Bulbs() { // Good const [on, setOn] &#61; useState(false); // ...}

以相同的顺序正确地调用多个useState()调用:

function Bulbs() { // Good const [on, setOn] &#61; useState(false); const [count, setCount] &#61; useState(1); // ...

useState()在自定义钩子的顶层被正确调用

function toggleHook(initial) { // Good const [on, setOn] &#61; useState(initial); return [on, () &#61;> setOn(!on)];}function Bulbs() { const [on, toggle] &#61; toggleHook(false); // ...}

useState() 的无效调用

在条件中调用useState()是不正确的&#xff1a;

function Switch({ isSwitchEnabled }) { if (isSwitchEnabled) { // Bad const [on, setOn] &#61; useState(false); } // ...}

在嵌套函数中调用useState()也是不对的

function Switch() { let on &#61; false;  let setOn &#61; () &#61;> {}; function enableSwitch() { // Bad [on, setOn] &#61; useState(false);  } return ( );}

4.2 过时状态

闭包是一个从外部作用域捕获变量的函数。

闭包&#xff08;例如事件处理程序&#xff0c;回调&#xff09;可能会从函数组件作用域中捕获状态变量。由于状态变量在渲染之间变化&#xff0c;因此闭包应捕获具有最新状态值的变量。否则&#xff0c;如果闭包捕获了过时的状态值&#xff0c;则可能会遇到过时的状态问题。

来看看一个过时的状态是如何表现出来的。组件延迟3秒计数按钮点击的次数。

function DelayedCount() { const [count, setCount] &#61; useState(0);
const handleClickAsync &#61; () &#61;> { setTimeout(function delay() { setCount(count &#43; 1); }, 3000);  } return (

{count}
);}

打开演示&#xff0c;快速多次点击按钮。count 变量不能正确记录实际点击次数&#xff0c;有些点击被吃掉。

delay() 是一个过时的闭包&#xff0c;它从初始渲染&#xff08;使用0初始化时&#xff09;中捕获了过时的count变量。

为了解决这个问题&#xff0c;使用函数方法来更新count状态&#xff1a;

function DelayedCount() { const [count, setCount] &#61; useState(0);
const handleClickAsync &#61; () &#61;> { setTimeout(function delay() { setCount(count &#61;> count &#43; 1); }, 3000); }
return (

{count}
);}

现在etCount(count &#61;> count &#43; 1)delay()中正确更新计数状态。React 确保将最新状态值作为参数提供给更新状态函数&#xff0c;过时闭包的问题解决了。

打开演示&#xff0c;快速单击按钮。延迟过去后&#xff0c;count 能正确表示点击次数。

4.3 复杂状态管理

useState()用于管理简单状态。对于复杂的状态管理&#xff0c;可以使用useReducer() hook。它为需要多个状态操作的状态提供了更好的支持。

假设需要编写一个最喜欢的电影列表。用户可以添加电影&#xff0c;也可以删除已有的电影&#xff0c;实现方式大致如下&#xff1a;

import React, { useState } from &#39;react&#39;;function FavoriteMovies() {  const [movies, setMovies] &#61; useState([{ name: &#39;Heat&#39; }]);  const add &#61; movie &#61;> setMovies([...movies, movie]); const remove &#61; index &#61;> { setMovies([ ...movies.slice(0, index), ...movies.slice(index &#43; 1) ]);  } return ( // Use add(movie) and remove(index)... );}

尝试演示&#xff1a;添加和删除自己喜欢的电影。

状态列表需要几个操作:添加和删除电影&#xff0c;状态管理细节使组件混乱。

更好的解决方案是将复杂的状态管理提取到reducer中&#xff1a;

import React, { useReducer } from &#39;react&#39;;function reducer(state, action) { switch (action.type) { case &#39;add&#39;: return [...state, action.item]; case &#39;remove&#39;: return [ ...state.slice(0, action.index), ...state.slice(action.index &#43; 1) ]; default: throw new Error(); }}function FavoriteMovies() { const [state, dispatch] &#61; useReducer(reducer, [{ name: &#39;Heat&#39; }]);
return ( // Use dispatch({ type: &#39;add&#39;, item: movie }) // and dispatch({ type: &#39;remove&#39;, index })... );}

reducer管理电影的状态&#xff0c;有两种操作类型&#xff1a;

  • "add"将新电影插入列表

  • "remove"从列表中按索引删除电影

尝试演示并注意组件功能没有改变。但是这个版本的更容易理解&#xff0c;因为状态管理已经被提取到reducer中。

还有一个好处:可以将reducer 提取到一个单独的模块中&#xff0c;并在其他组件中重用它。另外&#xff0c;即使没有组件&#xff0c;也可以对reducer 进行单元测试。

这就是关注点分离的威力:组件渲染UI并响应事件&#xff0c;而reducer 执行状态操作。

4.4 状态 vs 引用

考虑这样一个场景:咱们想要计算组件渲染的次数。

一种简单的实现方法是初始化countRender状态&#xff0c;并在每次渲染时更新它(使用useEffect() hook)

import React, { useState, useEffect } from &#39;react&#39;;function CountMyRenders() {  const [countRender, setCountRender] &#61; useState(0); useEffect(function afterRender() { setCountRender(countRender &#61;> countRender &#43; 1);  }); return (

I&#39;ve rendered {countRender} times
);}

useEffect()在每次渲染后调用afterRender()回调。但是一旦countRender状态更新&#xff0c;组件就会重新渲染。这将触发另一个状态更新和另一个重新渲染&#xff0c;依此类推。

可变引用useRef()保存可变数据&#xff0c;这些数据在更改时不会触发重新渲染&#xff0c;使用可变的引用改造一下 &#xff1a;

import React, { useRef, useEffect } from &#39;react&#39;;function CountMyRenders() {  const countRenderRef &#61; useRef(1); useEffect(function afterRender() { countRenderRef.current&#43;&#43;;  }); return (

I&#39;ve rendered {countRenderRef.current} times
);}

打开演示并单击几次按钮来触发重新渲染。

每次渲染组件时&#xff0c;countRenderRef可变引用的值都会使countRenderRef.current &#43;&#43;递增。重要的是&#xff0c;更改不会触发组件重新渲染。

5. 总结

要使函数组件有状态&#xff0c;请在组件的函数体中调用useState()

useState(initialState)的第一个参数是初始状态。返回的数组有两项:当前状态和状态更新函数。

const [state, setState] &#61; useState(initialState);

使用 setState(newState)来更新状态值。另外&#xff0c;如果需要根据先前的状态更新状态&#xff0c;可以使用回调函数setState(prevState &#61;> newState)

在单个组件中可以有多个状态:调用多次useState()

当初始状态开销很大时&#xff0c;延迟初始化很方便。使用计算初始状态的回调调用useState(computeInitialState)&#xff0c;并且此回调仅在初始渲染时执行一次。

必须确保使用useState()遵循 Hook 规则。

当闭包捕获过时的状态变量时&#xff0c;就会出现过时状态的问题。可以通过使用一个回调来更新状态来解决这个问题&#xff0c;这个回调会根据先前的状态来计算新的状态。

最后&#xff0c;您将使用useState()来管理一个简单的状态。为了处理更复杂的状态&#xff0c;一个更好的的选择是使用useReducer() hook。


推荐阅读
  • 目录浏览漏洞与目录遍历漏洞的危害及修复方法
    本文讨论了目录浏览漏洞与目录遍历漏洞的危害,包括网站结构暴露、隐秘文件访问等。同时介绍了检测方法,如使用漏洞扫描器和搜索关键词。最后提供了针对常见中间件的修复方式,包括关闭目录浏览功能。对于保护网站安全具有一定的参考价值。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • 现在比较流行使用静态网站生成器来搭建网站,博客产品着陆页微信转发页面等。但每次都需要对服务器进行配置,也是一个重复但繁琐的工作。使用DockerWeb,只需5分钟就能搭建一个基于D ... [详细]
  • JavaScript和Python是用于构建各种应用程序的两种有影响力的编程语言。尽管JavaScript多年来一直是占主导地位的编程语言,但Python的迅猛发展有 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • 我正在尝试使用scrapycrallsingle运行完美运行的scrapy蜘蛛,但我无法在python脚本中运行它.主要问题是从不执行SingleBlogSpider.parse方 ... [详细]
  • ReactJSUIAnt设计空组件原文:https://w ... [详细]
  • Skywalking系列博客1安装单机版 Skywalking的快速安装方法
    本文介绍了如何快速安装单机版的Skywalking,包括下载、环境需求和端口检查等步骤。同时提供了百度盘下载地址和查询端口是否被占用的命令。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
author-avatar
妩媚天天想我
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有