{"action":"opened","issue":{"url":"https://api.github.com/repos/garinghu/myblog/issues/8","repository_url":"https://api.github.com/repos/garinghu/myblog","labels_url":"https://api.github.com/repos/garinghu/myblog/issues/8/labels{/name}","comments_url":"https://api.github.com/repos/garinghu/myblog/issues/8/comments","events_url":"https://api.github.com/repos/garinghu/myblog/issues/8/events","html_url":"https://github.com/garinghu/myblog/issues/8","id":326796304,"number":8,"title":"React中的事务与批量更新","user":{"login":"garinghu","id":27051893,"avatar_url":"https://avatars2.githubusercontent.com/u/27051893?v=4","gravatar_id":"","url":"https://api.github.com/users/garinghu","html_url":"https://github.com/garinghu","followers_url":"https://api.github.com/users/garinghu/followers","following_url":"https://api.github.com/users/garinghu/following{/other_user}","gists_url":"https://api.github.com/users/garinghu/gists{/gist_id}","starred_url":"https://api.github.com/users/garinghu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/garinghu/subscriptions","organizations_url":"https://api.github.com/users/garinghu/orgs","repos_url":"https://api.github.com/users/garinghu/repos","events_url":"https://api.github.com/users/garinghu/events{/privacy}","received_events_url":"https://api.github.com/users/garinghu/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-05-27T08:00:23Z","updated_at":"2018-05-27T08:00:23Z","closed_at":null,"author_association":"OWNER","body":"写了这么长事件react，是时候了解一下原理了，看了很多源码解析，大致理解了react事务和批量更新机制，这里不贴源码（或少贴），以图的方式进行讲解，力求简单易懂\r\n\r\n### 一个都见过的例子\r\n\r\n```javascript\r\nclass Root extends React.Component {\r\n  constructor(props) {\r\n    super(props);\r\n    this.state = {\r\n      count: 0\r\n    };\r\n  }\r\n  componentDidMount() {\r\n    let me = this;\r\n    me.setState({\r\n      count: me.state.count + 1\r\n    });\r\n    console.log(me.state.count);    // 打印出0\r\n    me.setState({\r\n      count: me.state.count + 1\r\n    });\r\n    console.log(me.state.count);    // 打印出0\r\n    setTimeout(function(){\r\n     me.setState({\r\n       count: me.state.count + 1\r\n     });\r\n     console.log(me.state.count);   // 打印出2\r\n    }, 0);\r\n    setTimeout(function(){\r\n     me.setState({\r\n       count: me.state.count + 1\r\n     });\r\n     console.log(me.state.count);   // 打印出3\r\n    }, 0);\r\n  }\r\n  render() {\r\n    return (\r\n      <h1>{this.state.count}</h1>\r\n    )\r\n  }\r\n}\r\n```\r\n\r\n输出结果为0 0 2 3\r\n\r\n为啥呢，对批量更新有点概念的同学肯定知道前两次输出是0，因为react中setstate是异步的，在Didmount中多次setstate会在最后转为一次更新，但后两次为啥就更新了？在讲解批量更新之前就要先了解一下**事务（transaction）**的概念\r\n\r\n### react的transaction\r\n数据库学的不好，所以最开始接触事务这个词并没有像网上的大佬那样一下子想到sql的处理批量操作机制，所以咱在下文提到的transaction只是react中的transaction，跟别的事务没关系:)\r\n\r\ntransaction大致可以理解为一些要按照顺序执行的操作的集合，React中就是调用perform方法进入一个事务，该方法中会传入一个method参数。执行perform时先执行initializeAll方法按顺序执行一系列initialize的操作，例如一些初始化操作等等，然后执行传入的method，method执行完后就执行closeAll方法按顺序执行一系列close操作然后结束这次事务。\r\n> 注意，同一种事务不能同时开启，否则会抛出异常。我们还是回到我们上面的案例中来说明这个过程。\r\n\r\n大致可以这样理解\r\n\r\n执行perform(method)开始一个trensaction -> 执行perform中的init方法初始化 -> 执行传入的method -> 执行perform中的close方法 -> 结束transaction\r\n\r\n其中componentDidmount从触发到更新就是一个transaction，下图会详细描述大致的过程\r\n\r\n![myblog/images/transaction1.png](https://github.com/garinghu/myblog/raw/master/images/transaction1.png)\r\n\r\n这张图基本解释了以下几点\r\n- 为什么前两次输出为0：只有在componentDidmount执行结束时才会调用批量更新，这两个setState的作用只是将更新计划存到pendingStateQueue中\r\n\r\n- 为什么后两次会输出2和3：setTimeout为异步函数，是在componentDidmount结束后开始执行，而在componnentDidmount执行后会立即执行close结束transaction，所以后两次和前两次并不在一个transaction中，至于为什么输出不同，会在下面说\r\n\r\n\r\nclose()函数都干了什么？\r\n- 执行批量更新\r\n- 将isBatchingUpdata置为false\r\n\r\n\r\n这时就能理解为什么后两次会输出2和3了，因为每个setTimeout都是一个新的transaction，而这个transaction的method是enqueueUpdate，执行完成后会立即触发更新，所以会输出2和3\r\n\r\n上面说过在transaction即将结束时会调用批量更新操作，即多个setState只在一次渲染，下图会解释是怎么做到的\r\n\r\n![myblog/images/transaction1.png](https://github.com/garinghu/myblog/raw/master/images/transaction2.png)\r\n\r\n> dirtyComponents进入下一次循环时，执行performUpdateIfNecessary不会再去更新组件，这就实现了批量更新，即只做一次更新操作，React在更新组件时就是用这种方式做了优化\r\n\r\n其实这一步会出现一个问题，看一下下面的代码\r\n\r\n```javascript\r\nclass Root extends React.Component {\r\n  constructor(props) {\r\n    super(props);\r\n    this.state = {\r\n      count: 0\r\n    };\r\n  }\r\n  componentDidMount() {\r\n    let me = this;\r\n    me.setState({\r\n      count: me.state.count + 1\r\n    });\r\n    me.setState({\r\n      count: me.state.count + 1\r\n    });\r\n  }\r\n  render() {\r\n    return (\r\n      <h1>{this.state.count}</h1>   //页面中将打印出1\r\n    )\r\n  }\r\n}\r\n\r\nclass Root extends React.Component {\r\n  constructor(props) {\r\n    super(props);\r\n    this.state = {\r\n      count: 0\r\n    };\r\n  }\r\n  componentDidMount() {\r\n    let me = this;\r\n    me.setState(function(state, props) {\r\n      return {\r\n        count: state.count + 1\r\n      }\r\n    });\r\n    me.setState(function(state, props) {\r\n      return {\r\n        count: state.count + 1\r\n      }\r\n    });\r\n  }\r\n  render() {\r\n    return (\r\n      <h1>{this.state.count}</h1>   //页面中将打印出2\r\n    )\r\n  }\r\n}\r\n```\r\n这是为何？上文说到当传入的参数为函数时会先执行并返回obj再进行合并，这就需要看看函数是怎么执行的，贴下源码\r\n```javascript\r\n_processPendingState: function (props, context) {\r\n    var inst = this._instance;    // _instance保存了Constructor的实例，即通过ReactClass创建的组件的实例\r\n    var queue = this._pendingStateQueue;\r\n    var replace = this._pendingReplaceState;\r\n    this._pendingReplaceState = false;\r\n    this._pendingStateQueue = null;\r\n\r\n    if (!queue) {\r\n      return inst.state;\r\n    }\r\n\r\n    if (replace && queue.length === 1) {\r\n      return queue[0];\r\n    }\r\n\r\n    var nextState = _assign({}, replace ? queue[0] : inst.state);\r\n    for (var i = replace ? 1 : 0; i < queue.length; i++) {\r\n      var partial = queue[i];\r\n      _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);\r\n    }\r\n\r\n    return nextState;\r\n  },\r\n```\r\n会发现function实在inst语境下执行的，该语境下会对nextState进行更新，而通过this.state.count直接访问的方法并不会更新state，所以两次取到的是相同的值\r\n\r\n\r\n\r\n### 最后\r\n本文参考了[Re:深入研究React setState的工作机制](https://www.cnblogs.com/danceonbeat/p/6993674.html)原文中贴了很多源码，这篇文章相当于我理解后的总结吧\r\n\r\n\r\n"}}
{"actor":{"display_login":"garinghu"}}