原文: https://medium.com/@mweststra...
做者: Michel Weststratejavascript
这篇文章原标题是3 Reasons why I stopped using React.setState,可是我对原文做者提出的论点不是很感冒,可是做者提出的三点对React
新手来讲是很容易忽略的地方,因此我在这里只提出部份内容,并且把标题改成使用React.setState须要注意的三点。html
对React
新手来讲,使用setState
是一件很复杂的事情。即便是熟练的React
开发,也颇有可能由于React
的一些机制而产生一些bug,好比下面这个例子:java
文档中也说明了当使用setState
的时候,须要注意什么问题:react
注意:
绝对不要 直接改变this.state
,由于以后调用setState()
可能会替换掉你作的改变。把this.state
当作是不可变的。git
setState()
不会马上改变this.state
,而是建立一个即将处理的state
转变。在调用该方法以后访问this.state
可能会返回现有的值。github对
setState
的调用没有任何同步性的保证,而且调用可能会为了性能收益批量执行。app
setState()
将老是触发一次重绘,除非在shouldComponentUpdate()
中实现了条件渲染逻辑。若是可变对象被使用了,但又不能在shouldComponentUpdate()
中实现这种逻辑,仅在新state
和以前的state
存在差别的时候调用setState()
能够避免没必要要的从新渲染。异步
总结出来,当使用setState
的时候,有三个问题须要注意:函数
不少开发刚开始没有注意到setState
是异步的。若是你修改一些state
,而后直接查看它,你会看到以前的state
。这是setState
中最容易出错的地方。 setState
这个词看起来并不像是异步的,因此若是你不假思索的用它,可能会形成bugs
。下面这个例子很好的展现了这个问题:性能
class Select extends React.Component { constructor(props, context) { super(props, context) this.state = { selection: props.values[0] }; } render() { return ( <ul onKeyDown={this.onKeyDown} tabIndex={0}> {this.props.values.map(value => <li className={value === this.state.selection ? 'selected' : ''} key={value} onClick={() => this.onSelect(value)} > {value} </li> )} </ul> ) } onSelect(value) { this.setState({ selection: value }) this.fireOnSelect() } onKeyDown = (e) => { const {values} = this.props const idx = values.indexOf(this.state.selection) if (e.keyCode === 38 && idx > 0) { /* up */ this.setState({ selection: values[idx - 1] }) } else if (e.keyCode === 40 && idx < values.length -1) { /* down */ this.setState({ selection: values[idx + 1] }) } this.fireOnSelect() } fireOnSelect() { if (typeof this.props.onSelect === "function") this.props.onSelect(this.state.selection) /* not what you expected..*/ } } ReactDOM.render( <Select values={["State.", "Should.", "Be.", "Synchronous."]} onSelect={value => console.log(value)} />, document.getElementById("app") )
第一眼看上去,这个代码彷佛没有什么问题。两个事件处理中调用onSelect
方法。可是,这个Select
组件中有一个bug
很好的展示了以前的GIF
图。onSelect
方法永远传递的是以前的state.selection
值,由于当fireOnSelect
调用的时候,setState
尚未完成它的工做。我认为React
至少要把setState
更名为scheduleState
或者把回掉函数设为必须参数。
这个bug很容易修改,最难的地方在于你要知道有这个问题。
setState
形成的第二个问题是:每次调用都会形成从新渲染。不少时候,这些从新渲染是没必要要的。你能够用React performance tools
中的printWasted来查看何时会发生没必要要渲染。可是,大概的说,没必要要的渲染有如下几个缘由:
新的state
其实和以前的是同样的。这个问题一般能够经过shouldComponentUpdate
来解决。也能够用pure render
或者其余的库来解决这个问题。
一般发生改变的state
是和渲染有关的,可是也有例外。好比,有些数据是根据某些状态来显示的。
第三,有些state
和渲染一点关系都没有。有一些state
多是和事件、timer ID
有关的。
基于上面的最后一条,并非全部的组件状态都应该用setState
来进行保存和更新的。复杂的组件可能会有各类各样的状态须要管理。用setState
来管理这些状态不但会形成不少不须要的从新渲染,也会形成相关的生命周期钩子一直被调用,从而形成不少奇怪的问题。
在原文中做者推荐了一个叫作MobX
的库来管理部分状态,我不是很感冒,因此我就不介绍。若是感兴趣的,能够经过最上面的连接看看原文中的介绍。
基于上面提出的三点,我认为新手应该注意的地方是:
setState
是不保证同步的setState
是不保证同步的,是不保证同步的,是不保证同步的。重要的事情说三遍。之因此不说它是异步的,是由于setState
在某些状况下也是同步更新的。能够参考这篇文章
若是须要在setState
后直接获取修改后的值,那么有几个方案:
this.state
获取针对于以前的例子,彻底能够在调用fireOnSelect
的时候,传入须要的值。而不是在方法中在经过this.state
来获取
setState
方法接收一个function
做为回调函数。这个回掉函数会在setState
完成之后直接调用,这样就能够获取最新的state
。对于以前的例子,就能够这样:
this.setState({ selection: value }, this.fireOnSelect)
在setState
使用setTimeout
来让setState
先完成之后再执行里面内容。这样子:
this.setState({ selection: value }); setTimeout(this.fireOnSelect, 0);
直接输出,回调函数,setTimeout
对比
componentDidMount(){ this.setState({val: this.state.val + 1}, ()=>{ console.log("In callback " + this.state.val); }); console.log("Direct call " + this.state.val); setTimeout(()=>{ console.log("begin of setTimeout" + this.state.val); this.setState({val: this.state.val + 1}, ()=>{ console.log("setTimeout setState callback " + this.state.val); }); setTimeout(()=>{ console.log("setTimeout of settimeout " + this.state.val); }, 0); console.log("end of setTimeout " + this.state.val); }, 0); }
若是val默认为0, 输入的结果是:
> Direct call 0 > In callback 1 > begin of setTimeout 1 > setTimeout setState callback 2 > end of setTimeout 2 > setTimeout of settimeout 2
state
中来管理一般state
中只来管理和渲染有关的状态,从而保证setState
改变的状态都是和渲染有关的状态。这样子就能够避免没必要要的重复渲染。其余和渲染无关的状态,能够直接以属性的形式保存在组件中,在须要的时候调用和改变,不会形成渲染。或者参考原文中的MobX
。
避免没必要要的修改,当state
的值没有发生改变的时候,尽可能不要使用setState
。虽然shouldComponentUpdate
和PureComponent
能够避免没必要要的重复渲染,可是仍是增长了一层shallowEqual
的调用,形成多余的浪费。
以上