摘要:调用事务的方法,遍历待更新组件队列依次执行更新。执行生命周期,根据返回值判断是否要继续更新。三总结钩子函数和合成事件中在的生命周期和合成事件中,仍然处于他的更新机制中,这时为。这时将执行之前累积的。
一.几个开发中经常会遇到的问题
以下几个问题是我们在实际开发中经常会遇到的场景,下面用几个简单的示例代码来还原一下。
1.setState是同步还是异步的,为什么有的时候不能立即拿到更新结果而有的时候可以? 1.1 钩子函数和React合成事件中的setState现在有两个组件
componentDidMount() { console.log("parent componentDidMount"); } render() { return (); }
组件内部放入同样的代码,并在Setstate1中的componentDidMount中放入一段同步延时代码,打印延时时间:
componentWillUpdate() { console.log("componentWillUpdate"); } componentDidUpdate() { console.log("componentDidUpdate"); } componentDidMount() { console.log("SetState调用setState"); this.setState({ index: this.state.index + 1 }) console.log("state", this.state.index); console.log("SetState调用setState"); this.setState({ index: this.state.index + 1 }) console.log("state", this.state.index); }
下面是执行结果:
说明:
1.调用setState不会立即更新
2.所有组件使用的是同一套更新机制,当所有组件didmount后,父组件didmount,然后执行更新
3.更新时会把每个组件的更新合并,每个组件只会触发一次更新的生命周期。
1.2 异步函数和原生事件中的setstate?在setTimeout中调用setState(例子和在浏览器原生事件以及接口回调中执行效果相同)
componentDidMount() { setTimeout(() => { console.log("调用setState"); this.setState({ index: this.state.index + 1 }) console.log("state", this.state.index); console.log("调用setState"); this.setState({ index: this.state.index + 1 }) console.log("state", this.state.index); }, 0); }
执行结果:
说明:
1.在父组件didmount后执行
2.调用setState同步更新
2.为什么有时连续两次setState只有一次生效?分别执行以下代码:
componentDidMount() { this.setState({ index: this.state.index + 1 }, () => { console.log(this.state.index); }) this.setState({ index: this.state.index + 1 }, () => { console.log(this.state.index); }) }
componentDidMount() { this.setState((preState) => ({ index: preState.index + 1 }), () => { console.log(this.state.index); }) this.setState(preState => ({ index: preState.index + 1 }), () => { console.log(this.state.index); }) }
执行结果:
1 1
2 2
说明:
1.直接传递对象的setstate会被合并成一次
2.使用函数传递state不会被合并
二.setState执行过程由于源码比较复杂,就不贴在这里了,有兴趣的可以去github上clone一份然后按照下面的流程图去走一遍。
1.流程图图不清楚可以点击查看原图
partialState:setState传入的第一个参数,对象或函数
_pendingStateQueue:当前组件等待执行更新的state队列
isBatchingUpdates:react用于标识当前是否处于批量更新状态,所有组件公用
dirtyComponent:当前所有处于待更新状态的组件队列
transcation:react的事务机制,在被事务调用的方法外包装n个waper对象,并一次执行:waper.init、被调用方法、waper.close
FLUSH_BATCHED_UPDATES:用于执行更新的waper,只有一个close方法
2.执行过程对照上面流程图的文字说明,大概可分为以下几步:
1.将setState传入的partialState参数存储在当前组件实例的state暂存队列中。
2.判断当前React是否处于批量更新状态,如果是,将当前组件加入待更新的组件队列中。
3.如果未处于批量更新状态,将批量更新状态标识设置为true,用事务再次调用前一步方法,保证当前组件加入到了待更新组件队列中。
4.调用事务的waper方法,遍历待更新组件队列依次执行更新。
5.执行生命周期componentWillReceiveProps。
6.将组件的state暂存队列中的state进行合并,获得最终要更新的state对象,并将队列置为空。
7.执行生命周期componentShouldUpdate,根据返回值判断是否要继续更新。
8.执行生命周期componentWillUpdate。
9.执行真正的更新,render。
10.执行生命周期componentDidUpdate。
三.总结 1.钩子函数和合成事件中:在react的生命周期和合成事件中,react仍然处于他的更新机制中,这时isBranchUpdate为true。
按照上述过程,这时无论调用多少次setState,都会不会执行更新,而是将要更新的state存入_pendingStateQueue,将要更新的组件存入dirtyComponent。
当上一次更新机制执行完毕,以生命周期为例,所有组件,即最顶层组件didmount后会将isBranchUpdate设置为false。这时将执行之前累积的setState。
2.异步函数和原生事件中由执行机制看,setState本身并不是异步的,而是如果在调用setState时,如果react正处于更新过程,当前更新会被暂存,等上一次更新执行后在执行,这个过程给人一种异步的假象。
在生命周期,根据JS的异步机制,会将异步函数先暂存,等所有同步代码执行完毕后在执行,这时上一次更新过程已经执行完毕,isBranchUpdate被设置为false,根据上面的流程,这时再调用setState即可立即执行更新,拿到更新结果。
3.partialState合并机制我们看下流程中_processPendingState的代码,这个函数是用来合并state暂存队列的,最后返回一个合并后的state。
_processPendingState: function (props, context) { var inst = this._instance; var queue = this._pendingStateQueue; var replace = this._pendingReplaceState; this._pendingReplaceState = false; this._pendingStateQueue = null; if (!queue) { return inst.state; } if (replace && queue.length === 1) { return queue[0]; } var nextState = _assign({}, replace ? queue[0] : inst.state); for (var i = replace ? 1 : 0; i < queue.length; i++) { var partial = queue[i]; _assign(nextState, typeof partial === "function" ? partial.call(inst, nextState, props, context) : partial); } return nextState; },
我们只需要关注下面这段代码:
_assign(nextState, typeof partial === "function" ? partial.call(inst, nextState, props, context) : partial);
如果传入的是对象,很明显会被合并成一次:
Object.assign( nextState, {index: state.index+ 1}, {index: state.index+ 1} )
如果传入的是函数,函数的参数preState是前一次合并后的结果,所以计算结果是准确的。
4.componentDidMount调用setstate在componentDidMount()中,你 可以立即调用setState()。它将会触发一次额外的渲染,但是它将在浏览器刷新屏幕之前发生。这保证了在此情况下即使render()将会调用两次,用户也不会看到中间状态。谨慎使用这一模式,因为它常导致性能问题。在大多数情况下,你可以 在constructor()中使用赋值初始状态来代替。然而,有些情况下必须这样,比如像模态框和工具提示框。这时,你需要先测量这些DOM节点,才能渲染依赖尺寸或者位置的某些东西。
以上是官方文档的说明,不推荐直接在componentDidMount直接调用setState,由上面的分析:componentDidMount本身处于一次更新中,我们又调用了一次setState,就会在未来再进行一次render,造成不必要的性能浪费,大多数情况可以设置初始值来搞定。
当然在componentDidMount我们可以调用接口,再回调中去修改state,这是正确的做法。
当state初始值依赖dom属性时,在componentDidMount中setState是无法避免的。
5.componentWillUpdate componentDidUpdate这两个生命周期中不能调用setState。
由上面的流程图很容易发现,在它们里面调用setState会造成死循环,导致程序崩溃。
6.推荐使用方式在调用setState时使用函数传递state值,在回调函数中获取最新更新后的state。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/102018.html
摘要:原生事件中的在按钮原生事件中定义的和定时器效果一样,每次都会引起新的事件是合并的成一次的。原生事件事件生成计时器点击按钮,先执行原生事件,再执行事件,但是原生事件会触发两次,事件触发一次。 常规情况 在同一个方法中多次setState是会被合并的,并且对相同属性的设置只保留最后一次的设置; import React from react; export class Test exte...
摘要:除此之外指的是绕过通过直接添加的事件处理函数和产生的异步调用。但是,当在调用事件处理函数之前就会调用,这个函数会把修改为,造成的后果就是由控制的事件处理过程不会同步更新。 抛出问题 class Example extends Component { contructor () { super() this.state = { value: 0, ...
摘要:除此之外指的是绕过通过直接添加的事件处理函数和产生的异步调用。但是,当在调用事件处理函数之前就会调用,这个函数会把修改为,造成的后果就是由控制的事件处理过程不会同步更新。 抛出问题 class Example extends Component { contructor () { super() this.state = { value: 0, ...
摘要:根本原因在于,并不是真正意义上的异步操作,它只是模拟了异步的行为。而合成事件和生命周期函数中,是受控制的,其会将设置为,从而走的是类似异步的那一套。总结此处总结是直接引用了只在合成事件和钩子函数中是异步的,在原生事件和中都是同步的。 如何使用setState 在 React 日常的使用中,一个很重要的点就是,不要直接去修改 state。例如:this.state.count = 1是无...
阅读 2733·2021-11-16 11:44
阅读 945·2021-10-09 09:58
阅读 4445·2021-09-24 09:48
阅读 4008·2021-09-23 11:56
阅读 2382·2021-09-22 15:48
阅读 1846·2021-09-07 10:07
阅读 3165·2021-08-31 09:46
阅读 478·2019-08-30 15:56