热身准备
在正式讲useState
,我们先热热身,了解下必备知识。
为什么会有hooks
大家都知道hooks
是在函数组件的产物。之前class
组件为什么没有出现hooks
这种东西呢?
答案很简单,不需要。
因为在class
组件中,在运行时,只会生成一个实例,而在这个实例中会保存组件的state
等信息。在后续的更新操作中,也只是调用其中的render
方法,实例中的信息不会丢失。而在函数组件中,每次渲染,更新都会去执行这个函数组件,所以在函数组件中是没办法保存state
等信息的。为了保存state
等信息,于是有了hooks
,用来记录函数组件的状态,执行副作用。
hooks执行时机
上面提到,在函数组件中,每次渲染,更新都会去执行这个函数组件。所以我们在函数组件内部声明的hooks
也会在每次执行函数组件时执行。
在这个时候,可能有的同学听了我上面的说法(hooks
用来记录函数组件的状态,执行副作用),又有疑惑了,既然每次函数组件执行都会执行hooks
方法,那hooks
是怎么记录函数组件的状态的呢?
答案是,记录在函数组件对应的fiber
节点中。
两套hooks
在我们刚开始学习使用hooks
时,可能会有疑惑, 为什么hooks
要在函数组件的顶部声明,而不能在条件语句或内部函数中声明?
答案是,React
维护了两套hooks
,一套用来在项目初始化mount
时,初始化hooks
。而在后续的更新操作中会基于初始化的hooks
执行更新操作。如果我们在条件语句或函数中声明hooks
,有可能在项目初始化时不会声明,这样就会导致在后面的更新操作中出问题。
hooks存储
提前讲一下hooks存储方式,避免看晕了~~~
每个初始化的hook
都会创建一个hook
结构,多个hook
是通过声明顺序用链表的结构相关联,最终这个链表会存放在fiber.memoizedState
中:
var hook = {
memoizedState: null, // 存储hook操作,不要和fiber.memoizedState搞混了
baseState: null,
baseQueue: null,
queue: null, // 存储该hook本次更新阶段的所有更新操作
next: null // 链接下一个hook
};
而在每个hook.queue
中存放的么个update
也是一个链表结构存储的,千万不要和hook
的链表搞混了。
接下来,让我们带着下面几个问题看文章:
- 为什么
setState
后不能马上拿到最新的state
的值? - 多个
setState
是如何合并的? setState
到底是同步还是异步的?- 为什么
setState
的值相同时,函数组件不更新?
假如我们有下面这样一段代码:
function App(){
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(count => count + 1)
}
return (
<div>
勇敢牛牛, <span>不怕困难</span>
<span onClick={
handleClick}>{
count}</span>
</div>
)
}
初始化 mount
useState
我们先来看下useState()
函数:
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
上面的dispatcher
就会涉及到开始提到的两套hooks
的变换使用,initialState
是我们传入useState
的参数,可以是基础数据类型,也可以是函数,我们主要看dispatcher.useState(initialState)
方法,因为我们这里是初始化,它会调用mountState
方法:
function mountState(initialState) {
var hook = mountWorkInProgressHook(