重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
本篇内容介绍了“关于useState的知识点有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
网站的建设创新互联专注网站定制,经验丰富,不做模板,主营网站定制开发.小程序定制开发,H5页面制作!给你焕然一新的设计体验!已为混凝土搅拌站等企业提供专业服务。
hook如何保存数据
FunctionComponent的render本身只是函数调用。
那么在render内部调用的hook是如何获取到对应数据呢?
比如:
useState获取state
useRef获取ref
useMemo获取缓存的数据
答案是:
每个组件有个对应的fiber节点(可以理解为虚拟DOM),用于保存组件相关信息。
每次FunctionComponent render时,全局变量currentlyRenderingFiber都会被赋值为该FunctionComponent对应的fiber节点。
所以,hook内部其实是从currentlyRenderingFiber中获取状态信息的。
多个hook如何获取数据
我们知道,一个FunctionComponent中可能存在多个hook,比如:
function App() { // hookA const [a, updateA] = useState(0); // hookB const [b, updateB] = useState(0); // hookC const ref = useRef(0); return ; }
那么多个hook如何获取自己的数据呢?
答案是:
currentlyRenderingFiber.memoizedState中保存一条hook对应数据的单向链表。
对于如上例子,可以理解为:
const hookA = { // hook保存的数据 memoizedState: null, // 指向下一个hook next: hookB // ...省略其他字段 }; hookB.next = hookC; currentlyRenderingFiber.memoizedState = hookA;
当FunctionComponent render时,每执行到一个hook,都会将指向currentlyRenderingFiber.memoizedState链表的指针向后移动一次,指向当前hook对应数据。
这也是为什么React要求hook的调用顺序不能改变(不能在条件语句中使用hook) —— 每次render时都是从一条固定顺序的链表中获取hook对应数据的。
useState执行流程
我们知道,useState返回值数组第二个参数为改变state的方法。
在源码中,他被称为dispatchAction。
每当调用dispatchAction,都会创建一个代表一次更新的对象update:
const update = { // 更新的数据 action: action, // 指向下一个更新 next: null };
对于如下例子
function App() { const [num, updateNum] = useState(0); function increment() { updateNum(num + 1); } return{num}
; }
调用updateNum(num + 1),会创建:
const update = { // 更新的数据 action: 1, // 指向下一个更新 next: null // ...省略其他字段 };
如果是多次调用dispatchAction,例如:
function increment() { // 产生update1 updateNum(num + 1); // 产生update2 updateNum(num + 2); // 产生update3 updateNum(num + 3); }
那么,update会形成一条环状链表。
update3 --next--> update1 ^ | | update2 |______next_______|
这条链表保存在哪里呢?
既然这条update链表是由某个useState的dispatchAction产生,那么这条链表显然属于该useState hook。
我们继续补充hook的数据结构。
const hook = { // hook保存的数据 memoizedState: null, // 指向下一个hook next: hookForB // 本次更新以baseState为基础计算新的state baseState: null, // 本次更新开始时已有的update队列 baseQueue: null, // 本次更新需要增加的update队列 queue: null, };
其中,queue中保存了本次更新update的链表。
在计算state时,会将queue的环状链表剪开挂载在baseQueue最后面,baseQueue基于baseState计算新的state。
在计算state完成后,新的state会成为memoizedState。
为什么更新不基于memoizedState而是baseState,是因为state的计算过程需要考虑优先级,可能有些update优先级不够被跳过。所以memoizedState并不一定和baseState相同。更详细的解释见React技术揭秘[1]
回到我们开篇第一个问题:
function App() { const [num, updateNum] = useState(0); window.updateNum = updateNum; return num; }
调用window.updateNum(1)可以将视图中的0更新为1么?
我们需要看看这里的updateNum方法的具体实现:
updateNum === dispatchAction.bind(null, currentlyRenderingFiber, queue);
可见,updateNum方法即绑定了currentlyRenderingFiber与queue(即hook.queue)的dispatchAction。
上文已经介绍,调用dispatchAction的目的是生成update,并插入到hook.queue链表中。
既然queue作为预置参数已经绑定给dispatchAction,那么调用dispatchAction就步仅局限在FunctionComponent内部了。
update的action
第二个问题
function App() { const [num, updateNum] = useState(0); function increment() { setTimeout(() => { updateNum(num + 1); }, 1000); } return{num}
; }
在1秒内快速点击p5次,视图上显示为几?
我们知道,调用updateNum会产生update,其中传参会成为update.action。
在1秒内点击5次。在点击第五次时,第一次点击创建的update还没进入更新流程,所以hook.baseState还未改变。
那么这5次点击产生的update都是基于同一个baseState计算新的state,并且num变量也还未变化(即5次update.action(即num + 1)为同一个值)。
所以,最终渲染的结果为1。
useState与useReducer
那么,如何5次点击让视图从1逐步变为5呢?
由以上知识我们知道,需要改变baseState或者action。
其中baseState由React的更新流程决定,我们无法控制。
但是我们可以控制action。
action不仅可以传值,也可以传函数。
// action为值 updateNum(num + 1); // action为函数 updateNum(num => num + 1);
在基于baseState与update链表生成新state的过程中:
let newState = baseState; let firstUpdate = hook.baseQueue.next; let update = firstUpdate; // 遍历baseQueue中的每一个update do { if (typeof update.action === 'function') { newState = update.action(newState); } else { newState = action; } } while (update !== firstUpdate)
可见,当传值时,由于我们5次action为同一个值,所以最终计算的newState也为同一个值。
而传函数时,newState基于action函数计算5次,则最终得到累加的结果。
如果这个例子中,我们使用useReducer而不是useState,由于useReducer的action始终为函数,所以不会遇到我们例子中的问题。
事实上,useState本身就是预置了如下reducer的useReducer。
function basicStateReducer(state, action) { return typeof action === 'function' ? action(state) : action; }
“关于useState的知识点有哪些”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注创新互联网站,小编将为大家输出更多高质量的实用文章!