1.React setState 调用的原理
具体的执行过程如下(源码级解析):
- 首先调用了setState 入口函数,入口函数在这里就是充当一个分发器的角色,根据入参的不同,将其分发到不同的功能函数中去;
- ReactComponent.prototype.setState = function (partialState, callback) {
- this.updater.enqueueSetState(this, partialState);
- if (callback) {
- this.updater.enqueueCallback(this, callback, 'setState');
- }};
- enqueueSetState 方法将新的 state 放进组件的状态队列里,并调用 enqueueUpdate 来处理将要更新的实例对象;
- enqueueSetState: function (publicInstance, partialState) {
- // 根据 this 拿到对应的组件实例 var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
- // 这个 queue 对应的就是一个组件实例的 state 数组 var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
- queue.push(partialState);
- // enqueueUpdate 用来处理当前的组件实例 enqueueUpdate(internalInstance);}
- 在 enqueueUpdate 方法中引出了一个关键的对象——batchingStrategy,该对象所具备的isBatchingUpdates 属性直接决定了当下是要走更新流程,还是应该排队等待;如果轮到执行,就调用 batchedUpdates 方法来直接发起更新流程。由此可以推测,batchingStrategy 或许正是 React 内部专门用于管控批量更新的对象。
- function enqueueUpdate(component) {
- ensureInjected();
- // 注意这一句是问题的关键,isBatchingUpdates标识着当前是否处于批量创建/更新组件的阶段 if (!batchingStrategy.isBatchingUpdates) {
- // 若当前没有处于批量创建/更新组件的阶段,则立即更新组件 batchingStrategy.batchedUpdates(enqueueUpdate, component);
- return;
- }
- // 否则,先把组件塞入 dirtyComponents 队列里,让它“再等等” dirtyComponents.push(component);
- if (component._updateBatchNumber == null) {
- component._updateBatchNumber = updateBatchNumber + 1;
- }}
- 注意:batchingStrategy 对象可以理解为“锁管理器”。这里的“锁”,是指 React 全局唯一的 isBatchingUpdates变量,isBatchingUpdates 的初始值是 false,意味着“当前并未进行任何批量更新操作”。每当 React 调用 batchedUpdate 去执行更新动作时,会先把这个锁给“锁上”(置为 true),表明“现在正处于批量更新过程中”。当锁被“锁上”的时候,任何需要更新的组件都只能暂时进入 dirtyComponents 里排队等候下一次的批量更新,而不能随意“插队”。此处体现的“任务锁”的思想,是 React 面对大量状态仍然能够实现有序分批处理的基石。
2、React setState 调用之后发生了什么?是同步还是异步?
- React中setState后发生了什么
- 在代码中调用setState函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发调和过程(Reconciliation)。经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个UI界面。
在 React 得到元素树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染。在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。
如果在短时间内频繁setState。React会将state的改变压入栈中,在合适的时机,批量更新state和视图,达到提高性能的效果。
setState 是同步还是异步的- 假如所有setState是同步的,意味着每执行一次setState时(有可能一个同步代码中,多次setState),都重新vnode diff + dom修改,这对性能来说是极为不好的。如果是异步,则可以把一个同步代码中的多个setState合并成一次组件更新。所以默认是异步的,但是在一些情况下是同步的。
setState 并不是单纯同步/异步的,它的表现会因调用场景的不同而不同。在源码中,通过 isBatchingUpdates 来判断setState 是先存进 state 队列还是直接更新,如果值为 true 则执行异步操作,为 false 则直接更新。- 异步: 在 React 可以控制的地方,就为 true,比如在 React 生命周期事件和合成事件中,都会走合并操作,延迟更新的策略。
- 同步: 在 React 无法控制的地方,比如原生事件,具体就是在 addEventListener 、setTimeout、setInterval 等事件中,就只能同步更新。
- 一般认为,做异步设计是为了性能优化、减少渲染次数:
- setState设计为异步,可以显著的提升性能。如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;最好的办法应该是获取到多个更新,之后进行批量更新;
- 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步。state和props不能保持一致性,会在开发中产生很多的问题;
3、 React中setState的第二个参数作用是什么?
- setState 的第二个参数是一个可选的回调函数。这个回调函数将在组件重新渲染后执行。等价于在 componentDidUpdate 生命周期内执行。通常建议使用 componentDidUpdate 来代替此方式。在这个回调函数中你可以拿到更新后 state 的值:
this.setState({
key1: newState1,
key2: newState2,
...}, callback) // 第二个参数是 state 更新完成后的回调函数
4、在React中组件的this.state和setState有什么区别?
- this.state通常是用来初始化state的,this.setState是用来修改state值的。如果初始化了state之后再使用this.state,之前的state会被覆盖掉,如果使用this.setState,只会替换掉相应的state值。所以,如果想要修改state的值,就需要使用setState,而不能直接修改state,直接修改state之后页面是不会更新的