前言:我们在函数式组件中使用useState来为组件创建一个state,就能模拟类变量,这很神奇不是吗!用起来越方便,对它的了解就越模糊,本文是对useState的工作流程、数据结构的概括OwO
学习useState中产生的困惑
- useState如何在重复渲染情况下,保存状态值
- React如何管理多个state
- React如何在每次重新渲染时返回最新的状态
- 我们知道每个组件都可以使用useState,React又是如何管理不同组件的state
从问题出发
一.useState如何在重复渲染情况下,保存状态值
问题描述:在下面这个例子中,我们知道页面初次渲染,显示诸葛富贵,当我们点击按钮后,页面再次渲染,显示诸葛建国,那是不是说重渲染时const [name, setName] = useState('诸葛富贵')没有执行???
function App(){
console.log('App render')
const [name, setName] = useState('诸葛富贵')
return (
<div>
<h1>name: {name}</h1>
<button onClick={() => setName('诸葛建国')}>点我改名</button>
</div>
)
}
答案:这行代码执行了,控制台打印了App render,但是没用到初始值诸葛富贵,而是返回修改后的值,也就是说初始值只在第一次渲染时有效。React在运行const [name, setName] = useState('诸葛富贵')这行代码时,useState内部
会判断此处是初始渲染还是重渲染,如果是初始渲染,则返回初始值,如果是重渲染,计算出最新的state并返回。
示例: state: oldHook ? oldHook.state : initial,
2. React如何管理多个state
function App(){
console.log('App render')
const [name, setName] = useState('诸葛富贵')
const [name1, setName1] = useState('上官翠花')
const [name2, setName2] = useState('司马海味')
return (
<div>
<h1>name: {name}</h1>
<h1>name1: {name1}</h1>
<h1>name2: {name2}</h1>
</div>
)
}
管理Hook的是一个单向链表
1. 第一次渲染构建Hooks单向链表
第一次渲染App时,每运行一个useState(当然也可以是其他Hook,这里只用useState示例),都会向
单向链表添加一个节点,这个节点称为hook,初始化state,然后返回[state, setState]。
2. 重渲染阶段遍历Hooks单向链表
第一次渲染之后的称为重渲染,在重渲染阶段,我们就不需要再为添加节点,只需要从头节点遍历链表。
const [name, setName] = useState('诸葛富贵') 映射 Hooks[0] (链表的第一个节点),拿到state,并返回
const [name, setName] = useState('上官翠花') 映射 Hooks[1] (链表的第一个节点),拿到state,并返回
const [name, setName] = useState('司马海味') 映射 Hooks[2] (链表的第一个节点),拿到state,并返回
可以知道函数组件内的hook函数被React映射为一个链表,所以保持hook函数的执行顺序就是保持了正确的映射关系。
在React官方文档中有这么一段话: 只在最顶层使用 Hook 不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的
useState和useEffect调用之间保持 hook 状态的正确。
官方文档的这段话告诉我们两个信息:
1.不能在while、if、function中使用Hook
2. Hook的调用顺序很重要
3.React如何在每次重新渲染时返回最新的状态
问题描述:对于同一个状态,我们可以多次调用setState去修改状态,我们称一个setState为一个action,但是setState是异步修改,那么React肯定要先保存所以的action,之后在某一时机去调用action,修改状态,返回最新的状态。
function App(){
console.log('App render') //输出两次
const [name, setName] = useState('诸葛富贵')
const change = (oldName)=>{
setName('诸葛1')
setName('诸葛2')
setName('诸葛3')
console.log(name) //'诸葛富贵'
}
return (
<div>
<h1>name: {name}</h1>
<button onClick={change}>点我修改</button>
</div>
)
}
点击按钮,console.log('App render')输出了几次?console.log(name)?
答:输出了两次,初始化渲染+一次重渲染
管理action的结构是一个循环链表
每调用一次setName,都会向hook里的
queue添加一个节点,queue指向最后一个节点。
在update阶段,会从头节点开始遍历所有action,那么最后一次action就是最新的值。
这里有两点小疑问:
- 直接取最新的action的值不就可以了吗,为啥还要遍历之前的action。
答:因为action可能是一个函数,这需要之前action的状态作为参数。
- 既然遍历是从链表头开始的,那单链表也能实现,为啥要用循环链表?
答:如果使用单链表,想要找到最早的更新,就需要一层一层的找(这里我觉得保留头节点指针也可以鸭,为什么要一层一层的找呢),使用循环链表的话,
queue.last.next就是最早的action
4.我们知道每个组件都可以使用useState,React又是如何管理不同组件的state
答:React用fiber节点存放hooks单向链表