问题:setState 到底是同步还是异步的?
如果对 React 底层有一定了解,可以回答出 batchUpdate 批量更新概念,以及批量更新被打破的条件。
答案:有时是同步,有时是异步。
- 在 合成事件 和 生命周期函数 里是 异步 的
- 在 原生事件 和
setTimeout、promise 里是 同步 的
造成setState 的异步并不是由内部的异步代码引起的,在本身的执行过程中时同步的,但是合成事件和生命周期函数的调用顺序在更新之前,导致在内部不能直接得到更新后的值,可以用第二个参数 callback 来获取。
具体解释:可参考setState的执行过程
类组件state
setState(obj,callback)
- 第一个参数:当 obj 为一个对象,则为即将合并的 state ;如果 obj 是一个函数,那么当前组件的 state 和 props 将作为参数,返回值用于合并新的 state。
- 第二个参数 callback :callback 为一个函数,函数执行上下文中可以获取当前 setState 更新后的最新 state 的值,可以作为依赖 state 变化的副作用函数,可以用来做一些基于 DOM 的操作。
一次事件中触发一次如上 setState ,在 React 底层执行过程:
render 阶段 render 函数执行 -> commit 阶段真实 DOM 替换 -> setState 回调函数执行 callback
- 首先,setState 会产生当前更新的优先级(老版本用 expirationTime ,新版本用 lane )。
- 接下来 React 会从 fiber Root 根部 fiber 向下调和子节点,调和阶段将对比发生更新的地方,更新对比 expirationTime ,找到发生更新的组件,合并 state,然后触发 render 函数,得到新的 UI 视图层,完成 render 阶段。
- 接下来到 commit 阶段,commit 阶段,替换真实 DOM ,完成此次更新流程。
- 此时仍然在 commit 阶段,会执行 setState 中 callback 函数,到此为止完成了一次 setState 全过程。
setState原理揭秘
本质:React 底层调用 Updater 对象上的 enqueueSetState 方法
enqueueSetState() :创建一个update,放入当前 fiber对象 的待更新队列中,最后开启调度更新,进入更新流程。
React 的 batchUpdate 批量更新
目的:多次 setstate 会让逻辑多停留在 js 运行层面,阻塞了浏览器绘制,因此需要批量更新
batchedEventUpdates () :
分析流程:
- React 事件执行前通过 isBatchingEventUpdates=true 打开开关,开启事件批量更新
- 当事件结束,通过 isBatchingEventUpdates=false 关闭开关
- 在 scheduleUpdateOnFiber 中根据这个开关来确定是否进行批量更新
1)异步环境下,继续开启批量更新模式:
异步操作里面的批量更新规则会被打破,因此提供了手动批量更新方法: unstable_batchedUpdates
2)提升更新优先级:
提供了方法: flushSync ,可以将回调函数中的更新任务,放在一个较高的优先级中优先执行
补充:flushSync 在同步条件下,会合并之前的 setState | useState
3)总结:React 同一级别更新优先级 关系是:
flushSync 中的 setState > 正常执行上下文中 setState > 异步 setTimeout ,Promise 中的 setState
函数组件state
const [ state , dispatch ] = useState(initData)
- ① state 目的提供给 UI ,作为渲染视图的数据源
- ② dispatch 改变 state 的函数,可以理解为推动函数组件渲染的渲染函数
- ③ initData 初始值
initData的初始值
- 第一种情况是非函数,将作为 state 初始化的值
- 第二种情况是函数,函数的返回值作为 useState 初始化的值
dispatch的参数
- 第一种非函数情况,此时将作为新的值,赋予给 state,作为下一次渲染使用
- 第二种是函数的情况,如果 dispatch 的参数为一个函数,这里可以称它为reducer,reducer 参数,是上一次返回最新的 state,返回值作为新的 state
监听 state 变化
useEffect :常可以把 state 作为 依赖项 传入 useEffect 第二个参数 deps ,但是注意 useEffect 初始化会默认执行一次
dispatch 更新特点
与类组件一样,但是当调用改变 state 的函数dispatch,在本次函数执行上下文中,是获取不到最新的 state 值的
原因:函数组件更新就是函数的执行,在一次执行过程中,函数内部所有变量重新声明,所以改变的 state 只有在下一次函数执行时才更新。
useState 原理在之后 Hooks 讲解
问:类组件中的 setState 和函数组件中的 useState 有什么异同?
答:相同点:
- 原理:setState 和 useState 更新视图,底层都调用了 scheduleUpdateOnFiber 方法,而且事件驱动情况下都有批量更新规则
- 语法:第一个参数都可以传入函数
不同点:
- 在不是 pureComponent 组件模式下, setState 不会浅比较两次 state 的值。只要调用 setState 就会执行更新。但是 useState 中 dispatchAction 会默认比较两次state是否相同来更新组件
- setState 有专门监听 state 变化的回调函数 callback,可以获取最新 state。但是 useState 只能通过 useEffect 来执行 state 变化引起的副作用
- setState 在底层处理逻辑时将旧 state 进行合并处理,而 useState 是重新赋值
|