了解React中的setState
reacr组件通常都包含状态。状态可以是任何东西,想象一下使用场景:一个用户是否进行了登陆,根据账户的激活状态来展示准确的用户名。或者
是一系列博客文章。或者一个模态框是否打开以及其中的哪个标签是激活状态的。包含状态的React组件其渲染也依赖于这些状态。当组件的状态发生改变,组件也会
发生改变。
setState的工作机制
setState()
是在初始化状态之后唯一合法的更新状态的方式。比如我们有一个搜索组件,并且想展示用户提交的搜索内容。
如此设置:
1 | import React, { Component } from 'react' |
我们用一个空字符串来初始化状态,并且需要通过调用setState()
来更新状态searchTerm
。
1 | setState({ searchTerm: event.target.value }) |
此处,我们向setState()
传递了一个对象。这个对象包含了我们想要更新的这一部分状态,此处,也就是searchItem
的值。React获取这个值,然后将它合并到需要它的对象中。这有点像Search
组件在向searchTerm
状态索取它所需要的值,setState()
给了它这个答案。
这开启了一个React中叫做reconciliation
(调谐)的过程。调谐过程是React更新DOM的方式,根据状态的改变来改变组件。当setState()
触发时,React创建了一个包含组件中可响应元素(根据更新后的状态)的新树。通过这课树与前树的比较来确定Search
组件的UI如何根据状态的改变而响应式的发生变化。React知道要实现哪些更改,并且只会在必要时更新DOM的某些部分。这就是React很快的原因。
上面听起来有些多,所以总结成一些这些:
- 我们有一个展示搜索项的搜索组件。
- 搜索项目前为控。
- 用户提交了搜索内容。
- 搜索值被捕获然后被
setState()
保存到值中。 - 调谐进行然后React察觉到了值的变化。
- React指示搜索组件更新值然后将搜索项进行合并。
调谐过程没必要改变整棵树,除非一种情况发生,像下面这种树的根节点发生了变化。
1 | // old |
所有的<div>
标签变成了<span>
标签导致整棵组件树被刷新。
一条经验法则:永远不要直接改变state。使用setState()
来改变状态。像下面片段这样直接修改state是无法让组件重新渲染的。
1 | // do not do this |
向’setState()’传递一个函数
为了进一步的演示这个想法,让我们创建一个简单的计数器,通过点击来增加减少数字。
让我们来注册组件然后为UI定义标记。
1 | class App extends React.Component { |
这里,计数器只是简单的在每次点击后增加或者减少1。
假如如果我们想要每次增减3呢?我们或许会像下面这样尝试在handleIncrement
和handleDecrement
调用三次setState()
:
1 | handleIncrement = () => { |
如果你自己在家里编程,你可能惊讶的发现它并没有效果。
上面的代码片段相当于:
1 | Object.assign( |
Object.assign()
用于将数据从源对象拷贝到目标对象。如果拷贝的源对象有相同的键,像上例这样,后面的值会覆盖前面的值。这里有一个简单的Object.assign()
案例:
1 | let count = 3 |
所以调用不是执行了三次,而是一次。我们可以通过传递一个函数给setState()
来修复这个问题。正如你传递给setState()
一个对象,同样的也可以传递函数,这是解决上述情况的办法。
我们可以这样写handleIncrement
函数:
1 | handleIncrement = () => { |
现在我们就可以通过一次点击来增加数量三次了。
在这个案例中,React不会对其进行合并,而是根据函数的顺序执行,然后在函数全部执行完之后更新状态。这会将计数更新到3而不是1.
使用更新器访问之前的状态
当我们构建React应用时,经常会遇到需要通过之前的状态来己算现在的状态。我们并不能够在总是调用setState()
后,通过this.state
拿到准确的状态值,结果是它(this.state)总是和屏幕上渲染的状态相同。
让我们回到计数的例子中来看看它是如何工作的。假设我们有一个每次将计数递减1的函数。函数像下面这样:
1 | changeCount = () => { |
我们想要的是能够减去3,changeCount()
函数被绑定在点击事件上的调用了3次,像这样:
1 | handleDecrement = () => { |
每次减少的按钮被点击,计数减少了1而不是3。这是因为this.state.count
直到组件被重新渲染之后才进行了更新。解决方式是使用更新器。更新器允许你访问当前状态并立即使用它来更新其他项目。所以我们的changeCount()
函数应该是这个样子:
1 | changeCount = () => { |
现在我们不再依赖于this.state
的结果了。count
的状态彼此依赖,所以我们现在可以在每次调用changeCount()
之后获取到准确的状态值。
setState()
被视为异步————也就是说,不要总是期待调用setState()
之后,状态就会被改变。
总结
在使用setState()
时,你需要知道非常重要的几点:
- 一定使用
setState()
来更新组件状态 setState()
可以接收对象或者函数- 当多次更新状态时,传递一个函数
- 不要想在调用
setState()
后立即能够使用this.state获取准确的状态,代替的使用更新器来完成。