赞
踩
state和useState是react中很重要的概念,虽然笔者一直在用,但是总感觉有些地方认识不够透彻。于是乎,笔者重新阅读学习了react官方文档,感觉受益匪浅。希望能用尽量通俗简洁的语言把吸收的知识表述清楚,便写下此文。
如有错误,欢迎指正~
目录
当我们需要数据响应式渲染到页面上时,我们就不能使用普通的变量了:
此时,我们就需要useState来声明state去解决上面的问题。
官网中说到:“State是隔离且私有的”,那么这句话应该如何理解呢?
隔离:State 是屏幕上组件实例内部的状态。换句话说,如果你渲染同一个组件两次,每个副本都会有完全隔离的 state!改变其中一个不会影响另一个。
私有:与 props 不同,state 完全私有于<声明>它的组件。父组件无法更改它。
- //隔离:son组件渲染两次,两个son组件中的state互相隔离,互不影响
- //私有:parent不知道son组件中的state的任何信息,无法改变son组件的state(符合react的单项数据流动特性)
- <parent>
- <son/>
- <son/>
- </parent>
useState 返回一个由两个值组成的数组:
当前的 state:在首次渲染时,它将与你传递的 initialState 相匹配。
set 函数:它可以让你将 state 更新为不同的值并《触发重新渲染》。
const [state, setState] = useState(initialState);
在react中,触发组件渲染有两种情况:
在使用set函数之后,react会从该state声明(useState)的组件开始,根据更改后的state,重新渲染该组件&所有子组件。
为了更好的理解,我们可以把state理解为一张快照:当 React 调用你的组件时,它会为特定的那一次渲染提供一张 state 快照。你的组件会在其 JSX 中返回一张包含一整套新的 props 和事件处理函数的 UI 快照 ,其中所有的值都是 根据那一次渲染中 state 的值 被计算出来的。
为了更好的理解,我们接下来看几个例子
该例子是一个简易计数器,click事件里面调用了三次setNumber,当我们点击按钮之后会发生什么呢?
- //App.js
- import { useState } from 'react';
-
- export default function Counter() {
- const [number, setNumber] = useState(0);
-
- return (
- <>
- <h1>{number}</h1>
- <button onClick={() => {
- setNumber(number + 1);
- setNumber(number + 1);
- setNumber(number + 1);
- }}>+3</button>
- </>
- )
- }
点击之后发现,每次点击值只增加1,而非3。这是为什么呢?
我们来捋一下:
step1:该组件第一次渲染时,number的值是0,所以后续的渲染都是根据0这个值进行的计算。
step2:第一次组件渲染完成后,我们看下的onClick函数:
- <button onClick={() => {
- setNumber(number + 1);
- setNumber(number + 1);
- setNumber(number + 1);
- }}>+3</button>
以下是这个按钮的点击事件处理函数通知 React 要做的事情:
setNumber(number + 1)
:number
是 0
所以 setNumber(0 + 1)
。
number
更改为 1
。setNumber(number + 1)
:number
是0
所以 setNumber(0 + 1)
。
number
更改为 1
。setNumber(number + 1)
:number
是0
所以 setNumber(0 + 1)
。
number
更改为 1
。尽管你调用了三次 setNumber(number + 1)
,但在 这次渲染的 事件处理函数中 number
会一直是 0
,所以你会三次将 state 设置成 1
。这就是为什么在你的事件处理函数执行完以后,React 重新渲染的组件中的 number
等于 1
而不是 3
。
所以我们在第一次渲染之后,onClick函数可以等价为如下代码:
- <button onClick={() => {
- setNumber(0 + 1);
- setNumber(0 + 1);
- setNumber(0 + 1);
- }}>+3</button>
我们把上面的例子改一下,再预测一下会出现什么结果:
- //App.js
- import { useState } from 'react';
-
- export default function Counter() {
- const [number, setNumber] = useState(0);
-
- return (
- <>
- <h1>{number}</h1>
- <button onClick={() => {
- setNumber(number + 5);
- alert(number);
- }}>+5</button>
- </>
- )
- }
点击之后发现:数字从0变成5了,但是弹窗的数依旧是0。
根据我们上面提到的快照理解,上面的onClick代码可以等价为:
- <button onClick={() => {
- setNumber(0 + 5);
- alert(0);
- }}>+5</button>
这样是不是就很好理解了。
我们再改一下:如果我们加一个定时器,想让弹窗在组件重新渲染 之后 才触发,又会怎样呢?
- //App.js
- import { useState } from 'react';
-
- export default function Counter() {
- const [number, setNumber] = useState(0);
-
- return (
- <>
- <h1>{number}</h1>
- <button onClick={() => {
- setNumber(number + 5);
- setTimeout(() => {
- alert(number);
- }, 3000);
- }}>+5</button>
- </>
- )
结果: 数字从0变成5了,但是三秒之后的弹窗的数依旧是0。
这是为什么呢?我们看react官方如何解释:
“一个 state 变量的值永远不会在一次渲染的内部发生变化, 即使其事件处理函数的代码是异步的。”
“React 会使 state 的值始终”固定“在一次渲染的各个事件处理函数内部。”
state作为快照,它的值在 React 通过调用你的组件“获取 UI 的快照”时就被“固定”了,哪怕是异步代码,它得到的state值也是在调用时被固定的快照的值。
所以对应函数等价于如下代码:
- setNumber(0 + 5);
- setTimeout(() => {
- alert(0);
- }, 3000);
让我们回到例一,如果我们想在onClick里面写三个set函数来实现加3的效果,那该如何改造呢?
这时候就需要用到更新函数了:在set函数中,传入一个根据队列中的前一个 state 计算下一个 state 的 函数。
简单来说,就是把set函数的参数,从之前的一个值/变量,换成一个函数即可。
更改后的代码见下图:
这样,每点击一次,加的就是3了。
详细更新过程见下图:
我们再升级一下例4的代码,运行结果会怎样呢?
结果:点击按钮后,从0变成了42。
详细更新过程见下图:
setNumber(n => n + 1)
更新函数。Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。