几个月前,我开始中止使用React的 setState 。我并非再也不须要组件状态,并且再也不用React来管理个人组件状态。html
setState对于新手来讲不是很友好,即便是有经验的React程序员在使用setState时,也很容易出bug,好比:react
Bug产生的缘由是忘记了React的state是异步的;从日志打印延迟能够看出来。git
React的官方文档已将把使用setState可能会出现的全部问题都总结了:程序员
注意:github
永远不要直接修改
this.state
,须要经过调用this.setState
方法来替换你修改后的值。把this.state
当作不可变数据来处理。api
setState()
不会立刻去改变this.state
,而是会排队等待处理,因此当你调用setState()
后访问this.state
,有可能会返回旧的state
。bash当你调用
setState()
时,没法保证是同步执行的,由于为了保证性能可能会被批处理。数据结构
setState()
老是会触发render()
进行从新渲染,除非在shouldComponentUpdata()
控制了渲染逻辑。若是用了可变数据结构以及在shouldComponentUpdata()
中并无控制渲染逻辑,调用setState()
将不会触发从新渲染渲染。app
总的来讲,使用setState
会带来三个问题:异步
setState
是异步的许多开发人员起初并无意识到这一点,当你设置了新的state
,却发现没有变化,这是setState
最诡异的地方,由于setState
调用的时候看起来并非异步。好比下面的代码:
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")
)复制代码
乍一看没什么问题,可是这个select
组件有一个bug,上面的git图已经很好的证实了。onSelect()
方法触发时老是获得前一个state.selection
的值,由于setState
尚未完成,fireOnSelect
就被调用了。我认为应该把setState
从新命名为scheduleState
或者要求传入回调。
这个bug很容易修复,棘手的地方在于你很难发现它。
setState
引发没有必要的渲染setState
的第二个问题在于它老是会触发从新渲染,不少时候这种渲染是没有必要的。你能够经过printWasted(React提供的性能工具)来检测它会在何时从新渲染。从三个方面来粗略的讲为何从新渲染有时候是没有必要的:
当新的state
和旧的state
是同样的。能够经过shouldComponentUpdate
来解决,也能够用纯渲染库来解决。
只有某些时候state
的改变才和渲染有关系。
第三,某些时候state
和视图层一点关系都没有,好比用来管理事件的监听器,定时器等相关的state
。
setState
不可能管理全部组件的状态接着上面最后那点说,不是全部的组件状态都须要经过setState
来储存和更新。大多数复杂的组件一般须要管理定时器循环,接口请求,事件等等。若是用setState
来管理,不只仅会引发没有必要渲染,并且会形成死循环。
代码以下:
import {observable} from "mobx"
import {observer} from "mobx-react"
@observer class Select extends React.Component {
@observable selection = null; /* MobX managed instance state */
constructor(props, context) {
super(props, context)
this.selection = props.values[0]
}
render() {
return (
<ul onKeyDown={this.onKeyDown} tabIndex={0}>
{this.props.values.map(value =>
<li
className={value === this.selection ? 'selected' : ''}
key={value}
onClick={() => this.onSelect(value)}
>
{value}
</li>
)}
</ul>
)
}
onSelect(value) {
this.selection = value
this.fireOnSelect()
}
onKeyDown = (e) => {
const {values} = this.props
const idx = values.indexOf(this.selection)
if (e.keyCode === 38 && idx > 0) { /* up */
this.selection = values[idx - 1]
} else if (e.keyCode === 40 && idx < values.length -1) { /* down */
this.selection = values[idx + 1]
}
this.fireOnSelect()
}
fireOnSelect() {
if (typeof this.props.onSelect === "function")
this.props.onSelect(this.selection) /* solved! */
}
}
ReactDOM.render(
<Select
values={["State.", "Should.", "Be.", "Synchronous."]}
onSelect={value => console.log(value)}
/>,
document.getElementById("app")
)复制代码
效果以下:
用同步的组件状态的机制没有出现意想不到的bug。
上面的代码片断不只简洁美观,MobX
还解决了setState
的所有问题:
改变state
立刻就反映到了组件state
上。这让代码逻辑和代码重用变得更简单。你不用去担忧state
是否更新了。
MobX
在运行时候肯定哪些state
与视图层关联,暂时与视图层无关的State
不会引发从新渲染,直到再次关联。
因此可渲染和不可渲染状态是统一处理的。
此外,直接修改state
对象的低级错误不能再犯了。还有,不要担忧是否还能用shouldComponentUpdate 和或者PureRenderMixin方法,MobX
也很好的处理了。最后,你也许会想,我如何等到setState
处理完成呢,你可使用compentDidUpdate
生命周期。
我已经中止使用React来管理组件状态了,而是用MobX
代替。如今React
成了“正真”的视图层:)。
如下是实现的Dome: