setState最常见的问题是,是异步的还是同步的?
setState在React.Component中的函数,是同步函数。但是我们调用的时候,不同的传参和不同的调用位置都会导致不同的结果。
从页面看有时候是同步的,有时候是异步的。
PS: 如果在componentDidMount触发,和用按钮事件触发是一样的。
1)调用函数中直接调用,调用完之后立即打印,结果不变,表现是异步
this.state = {number: 0} add = () => { this.setState({ number: this.state.number + 1}, () => console.log(this.state.number) // 1 ) // console.log(this.state.number); -- 0 } // 页面显示1
后台逻辑是,事件触发-->开启批量更新模式-->执行事件方法add-->状态存入队列[{number: 1}]-->方法结束(this.state.number=0)-->关闭批量更新模式
-->更新状态(number-1)-->更新组件(render)-->componentDidUpdate-->setState.callback(number-1)
2)批量调用setState会触发批量更新
如第一个参数是对象,状态会出现覆盖;第一个参数是函数,不会被覆盖,会累计。
state = {number: 0, name: 'lyra'} add2 = () => { this.setState((state) => ({number: state.number + 1})); this.setState((state) => ({name: state.name + '-good'})); this.setState((state) => ({number: state.number + 1})); this.setState((state) => ({name: state.name + '-clever'})); }// 执行完结果显示{number:2, name: lyra-good-clever} add3 = () => { this.setState({number: this.state.number + 1}); this.setState({name: this.state.name + '-good'}); this.setState({number: this.state.number + 1}); this.setState({name: this.state.name + '-clever'}); } // 执行完结果显示{number: 1, name: lyra-clever}
后台逻辑同上,会依次存入状态队列。但是更新状态的方式是:
this.queue.pendingStates.forEach(particalState => Object.assign(this.state, particalState));
如果传入对象,存储状态的时候直接存储。
if (typeof particalState === 'object') { this.pendingStates.push(particalState); }
如果传入的是方法,会先进行处理,每个状态,都是以前一个状态为基础
if (typeof particalState === 'function') {// 传入的是(state) => {} this.setStateFuncs.push(particalState); this.setStateFuncs.forEach(callback => { const newParticalState = callback(this.component.state); this.pendingStates.push(newParticalState); Object.assign(this.component.state, newParticalState); }); this.setStateFuncs.length = 0;// 注意清空!! }
3)在异步任务回调函数中表现同步,且不批量更新
class Test extends React.Component { constructor(props) { super(props); this.state = { number: 0, name: 'lyra', } } componentDidMount() { this.add(); } add = () => { this.setState({number: this.state.number + 1}, () => console.log('callback-3=',this.state)); this.setState({name: this.state.name + '-good'}, () => console.log('callback-4=',this.state)); this.setState({number: this.state.number + 1}, () => console.log('callback-5=',this.state)); this.setState({name: this.state.name + '-clever'}, () => console.log('callback-6=',this.state)); console.log('==1==',this.state); setTimeout(() => { console.log("==7===",this.state); this.setState({ // 非批量处理,立即执行,触发render number: this.state.number + 1, name: this.state.name + '-bravo' }, () => console.log('callback-8=',this.state)) console.log('==9==',this.state); this.setState({ number: this.state.number + 1, name: this.state.name + '-congratulations' }, () => console.log('callback-10=',this.state)) console.log('==11==',this.state); }) console.log("==2==",this.state); } render() { console.log('render') return ( <h1>Hello, world!</h1> ) } }
运行结果如下
==1=={number: 0, name: 'lyra'} ==2=={number: 0, name: 'lyra'} render callback-3={number:1, name: 'lyra-clever} callback-4={number:1, name: 'lyra-clever} callback-5={number:1, name: 'lyra-clever} callback-6={number:1, name: 'lyra-clever} ==7==={number: 1, name: 'lyra-clever'} render callback-8={number: 2, name: 'lyra-clever-bravo'} ==9=={number: 2, name: 'lyra-clever-bravo'} render callback-10={number: 3, name: 'lyra-clever-bravo-congratulations'} ==11=={number: 3, name: 'lyra-clever-bravo-congratulations'}
后台逻辑: 事件触发-->开启批量更新模式-->执行事件方法主线程(不包含setTimeout宏任务)-->前四个状态存入队列{number: 1}-->主线程结束-->
关闭批量更新模式-->批量更新状态(this.state.number = 1)并渲染(DidUpdate)-->进入SetTImout-->setState-->非批量更新状态立即更新状态并渲染(DidUpdate)-->callback->
-->下一个setState-->......
PS:原生JS模拟setState实现
html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <div id="root"></div> <script src="./component.js"></script> <script src='./counter.js'></script> <script src="./stateFunc.js"></script> <script> let container = document.getElementById('root'); counterIns = new Counter({name: 'Lyra'}); //实例传属性 counterIns1 = new SetFuncCounter({name: 'Lyra1'}); //实例传属性 counterIns.mount(root); // ReactDOM.render(,window.root) counterIns1.mount(root); </script> </body> </html>
component.js
/* * @Author: LyraLee * @Date: 2019-11-18 23:49:56 * @LastEditTime: 2019-11-18 23:57:49 * @Description: */ // 用于批量更新的全局状态和方法 const batchingStragety = { isBatchingUpdates: false, // 默认false,表面默认直接更新 dirtyComponents: [], // 状态和页面展示不同的组件叫脏组件;应该去重一下 batchUpdates() { this.dirtyComponents.forEach(dirtyComponent => { dirtyComponent.updateComponent(); }); this.dirtyComponents.length = 0; } } // 队列用于存储更新的状态,数据先入先出,有先后顺序 class QueueUpdate{ constructor(component) { this.pendingStates = []; // 队列存值 this.setStateFuncs = []; // 传参是函数,存入该队列 this.component = component; } // 插入队列 addState(particalState) { // particalState有两种形式,function或者object; if (typeof particalState === 'object') { this.pendingStates.push(particalState); } if (typeof particalState === 'function') {// 传入的是(state) => {} this.setStateFuncs.push(particalState); this.setStateFuncs.forEach(callback => { const newParticalState = callback(this.component.state); this.pendingStates.push(newParticalState); Object.assign(this.component.state, newParticalState); }); this.setStateFuncs.length = 0;// 注意清空!! } /* 然后判断isBatchingUpdating 是否是true, 如果是true,推入dirtyComponents, */ // 如果是false, 遍历所有的dirtyComponents,调用各自的updateComponent() if (!batchingStragety.isBatchingUpdates) { this.component.updateComponent(); return; } batchingStragety.dirtyComponents.push(this.component); } } class Component { constructor(props) { this.props = props; this.queue = new QueueUpdate(this); // 实例化一个队列,用于存储newState的值。 } setState(particalState, callback) { // 传入需要更新的状态 /** this.setState(new State)->将newState存入队列 */ this.queue.addState(particalState); // callbackcomponentDidMount前存入队列, // 在componentDidMount后依次执行 } updateComponent() { // 更新组件,在该方法中完成componentDidMount this.queue.pendingStates.forEach(particalState => { Object.assign(this.state, particalState) }); this.queue.pendingStates.length = 0; let oldElement = this.topElement; let newElement = this.renderElement(); oldElement.parentNode.replaceChild(newElement, oldElement); } renderElement() { // 模拟ReactDOM.render(element, container) let tempParent = document.createElement('div'); tempParent.innerHTML = this.render(); // 将模版字符串转为真实dom this.topElement = tempParent.children[0]; // 使用属性,可以在this作用域暂存dom元素 this.topElement.component = this; return this.topElement; } mount(container) { container.appendChild(this.renderElement()) } } // React中事件委托给全局document window.trigger = function(event, method) { const { component } = event.target; // React中每次触发事件时开启批量更新模式,都默认将isBatchingUpdates置为true,事件调用完成置为false。 // 事件触发的核心逻辑代码是方法调用,isBatchingUpdates的状态变化可以看作一个事务,包裹方法 // 所有组件用这个方法,各个组件方法名可能相同,所以需要在实例上调用方法 const anyMethod = component[method].bind(component); transaction.perform(anyMethod); } // 事务,将任何方法(anyMethod)用wrapper包裹,然后通过Transcation的perform()方法执行anyMethod class Transcation{ // 先执行所有wrapper的initialize方法,再执行anyMethod,最后执行所有wrapper的close方法。 constructor(wrappers) { this.wrappers = wrappers; } perform(anyMethod) { this.wrappers.forEach(wrapper => {wrapper.initialize()}); anyMethod(); this.wrappers.forEach(wrapper => {wrapper.close()}); } } const transaction = new Transcation([{ // 传入wrappers,是个数组!! initialize() {// 开启批量更新 batchingStragety.isBatchingUpdates = true; }, close() { batchingStragety.isBatchingUpdates = false; // 执行完后要把状态置为false, 调用更新函数 batchingStragety.batchUpdates(); } }]);
counter.js
class Counter extends Component{ constructor(props) { super(props); this.state = { number: 0, name: 'lyra' } } add() { this.setState({number: this.state.number + 1}); this.setState({name: this.state.name + '-good'}); this.setState({number: this.state.number + 1}); this.setState({name: this.state.name + '-clever'}); console.log('1==',this.state); // {number: 0, name: 'lyra'} setTimeout(() => { console.log("==3===",this.state); // {number: 1, name: 'lyra-clever'} this.setState({ // 非批量处理,立即执行 number: this.state.number + 1, name: this.state.name + '-bravo' }) // 同步 console.log('==4==',this.state); // {number: 2, name: 'lyra-clever-bravo'} this.setState({ number: this.state.number + 1, name: this.state.name + '-congratulations' }) console.log('==5==',this.state); // {number: 3, name: 'lyra-clever-bravo-congratulations'} }) console.log("==2==",this.state); // {number: 0, name: 'lyra'} } render() {// 模拟JSX中虚拟DOM js中不能写html,只能写字符串模版,模版字符串只有一个顶级标签 return `<button id="counter" onclick="trigger(event, 'add')"> ${this.state.name} --------------<br/> ${this.state.number} </button>` } }