首先我们来看一个例子
import React, { useState } from 'react'
export default function () {
const [val, setVal] = useState(0)
const handleClick = () => {
setVal(val + 1)
setVal(val + 1)
setVal(val + 1)
}
return (
<div>
<button onClick={() => handleClick()}>you click me {val} times</button>
</div>
)
}
我们给组件设置了一个状态val,当点击按钮时set三次val+1如果我们点击按钮会发生什么呢?
val并不会像我们期望的那样从0变为3而是只会变为1 这是为什么呢?
首先我们先看一下官网的解释:
“正在渲染” 就意味着 React 正在调用你的组件——一个函数。你从该函数返回的 JSX 就像是 UI 的一张及时的快照。它的 props、事件处理函数和内部变量都是 根据当前渲染时的 state 被计算出来的。
与照片或电影画面不同,你返回的 UI “快照”是可交互的。它其中包括类似事件处理函数的逻辑,这些逻辑用于指定如何对输入作出响应。React 随后会更新屏幕来匹配这张快照,并绑定事件处理函数。因此,按下按钮就会触发你 JSX 中的点击事件处理函数。
当 React 重新渲染一个组件时:
- React 会再次调用你的函数
- 函数会返回新的 JSX 快照
- React 会更新界面以匹配返回的快照

作为一个组件的记忆,state 不同于在你的函数返回之后就会消失的普通变量。state 实际上“活”在 React 本身中——就像被摆在一个架子上!——位于你的函数之外。当 React 调用你的组件时,它会为特定的那一次渲染提供一张 state 快照。你的组件会在其 JSX 中返回一张包含一整套新的 props 和事件处理函数的 UI 快照 ,其中所有的值都是 根据那一次渲染中 state 的值 被计算出来的!

总结来说,当你调用函数触发状态更新时,React会生成当前状态的快照,所有更新状态的操作都会根据这个快照运行。
再看刚才的例子
import React, { useState } from 'react'
export default function () {
const [val, setVal] = useState(0)
const handleClick = () => {
// 执行此函数时 val的快照为0
setVal(val + 1) // val = 0 + 1 = 1
setVal(val + 1) // val = 0 + 1 = 1
setVal(val + 1) // val = 0 + 1 = 1
// 执行的结果均为1 所以最终结果为1
}
return (
<div>
<button onClick={() => handleClick()}>you click me {val} times</button>
</div>
)
}
上面讲了关于state快照的基本逻辑,那如何避免出现这种预料之外的情况呢?可以使用状态更新函数
还是上面的例子,我们进行如下修改
import React, { useState } from 'react'
export default function () {
const [val, setVal] = useState(0)
const handleClick = () => {
setVal((v) => v + 1)
setVal((v) => v + 1)
setVal((v) => v + 1)
}
return (
<div>
<button onClick={() => handleClick()}>you click me {val} times</button>
</div>
)
}
useState提供了更新函数的方式更新状态,这是一种告诉 React “用 state 值做某事”而不是仅仅替换它的方法。
这次再点击按钮时val将会如愿从0变为3
源码解析
我们知道useState有快照更新机制,但是React也提供了状态更新函数,为了理解其中的底层原理我们来看一下React的源码是如何实现的
我们将上面的例子整合一下看一下React内部具体是怎么处理的
function Demo() {
const [val, setVal] = React.useState(0)
const handleClick = () => {
setVal(val + 1)
setVal(val + 1)
setVal(val + 1)
setVal(v => v + 1)
setVal(v => v + 1)
setVal(v => v + 1)
}
return (
<div>
<button onClick={() => handleClick()}>you click me {val} times</button>
</div>
)
}
此时我们点击按钮,经过调试发现React内部生成了一个concurrentQueues队列,每次更新状态的操作会在队列里插入四个值

上述六个setVal运行完成后concurrentQueues队列的值为

我们重点关注update,可以看到update中有action,前三次非函数式更新时action的值是静态的,它是根据当前state(val)计算出来的,因为当前state(val)均为0所以计算出来的结果均为1,后三次的update.action传入的是(v) => v+1,后续React会通过调度器将update串联起来
我们再看具体执行更新state的地方

可以看到这里是内部缓存了一个newState变量的它记录就是更新后的状态值,后续通过reducer函数更新
reducer函数

这里可以看到函数内部判断了action的类别如果是函会将回调的结果返回,如果不是函数则将action直接返回,因为这里的参数state是更新后的newState所以当action是状态更新函数时,每次更新的参数就是最新的newState,这也就解释了为什么函数时更新是可以实时获取最新的state的
Comments NOTHING