Understanding-React-`setState`翻译

作者 Zwe1 日期 2018-05-29
Understanding-React-`setState`翻译

了解React中的setState

reacr组件通常都包含状态。状态可以是任何东西,想象一下使用场景:一个用户是否进行了登陆,根据账户的激活状态来展示准确的用户名。或者
是一系列博客文章。或者一个模态框是否打开以及其中的哪个标签是激活状态的。包含状态的React组件其渲染也依赖于这些状态。当组件的状态发生改变,组件也会
发生改变。

setState的工作机制

setState()是在初始化状态之后唯一合法的更新状态的方式。比如我们有一个搜索组件,并且想展示用户提交的搜索内容。
如此设置:

1
2
3
4
5
6
7
8
9
10
11
import React, { Component } from 'react'

class Search extends Component {
constructor(props) {
super(props)

state = {
searchTerm: ''
}
}
}

我们用一个空字符串来初始化状态,并且需要通过调用setState()来更新状态searchTerm

1
setState({ searchTerm: event.target.value })

此处,我们向setState()传递了一个对象。这个对象包含了我们想要更新的这一部分状态,此处,也就是searchItem的值。React获取这个值,然后将它合并到需要它的对象中。这有点像Search组件在向searchTerm状态索取它所需要的值,setState()给了它这个答案。

setState

这开启了一个React中叫做reconciliation(调谐)的过程。调谐过程是React更新DOM的方式,根据状态的改变来改变组件。当setState()触发时,React创建了一个包含组件中可响应元素(根据更新后的状态)的新树。通过这课树与前树的比较来确定Search组件的UI如何根据状态的改变而响应式的发生变化。React知道要实现哪些更改,并且只会在必要时更新DOM的某些部分。这就是React很快的原因。

上面听起来有些多,所以总结成一些这些:

  • 我们有一个展示搜索项的搜索组件。
  • 搜索项目前为控。
  • 用户提交了搜索内容。
  • 搜索值被捕获然后被setState()保存到值中。
  • 调谐进行然后React察觉到了值的变化。
  • React指示搜索组件更新值然后将搜索项进行合并。

调谐过程没必要改变整棵树,除非一种情况发生,像下面这种树的根节点发生了变化。

1
2
3
4
5
6
7
8
9
// old
<div>
<Search />
</div>

// new
<span>
<Search />
</span>

所有的<div>标签变成了<span>标签导致整棵组件树被刷新。

一条经验法则:永远不要直接改变state。使用setState()来改变状态。像下面片段这样直接修改state是无法让组件重新渲染的。

1
2
3
4
// do not do this
this.state = {
searchTerm: event.target.value
}

向’setState()’传递一个函数

为了进一步的演示这个想法,让我们创建一个简单的计数器,通过点击来增加减少数字。

让我们来注册组件然后为UI定义标记。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class App extends React.Component {

state = { count: 0 }

handleIncrement = () => {
this.setState({ count: this.state.count + 1 })
}

handleDecrement = () => {
this.setState({ count: this.state.count - 1 })
}
render() {
return (
<div>
<div>
{this.state.count}
</div>
<button onClick={this.handleIncrement}>Increment by 1</button>
<button onClick={this.handleDecrement}>Decrement by 1</button>
</div>
)
}
}

这里,计数器只是简单的在每次点击后增加或者减少1。

假如如果我们想要每次增减3呢?我们或许会像下面这样尝试在handleIncrementhandleDecrement调用三次setState():

1
2
3
4
5
6
7
8
9
10
11
handleIncrement = () => {
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
}

handleDecrement = () => {
this.setState({ count: this.state.count - 1 })
this.setState({ count: this.state.count - 1 })
this.setState({ count: this.state.count - 1 })
}

如果你自己在家里编程,你可能惊讶的发现它并没有效果。

上面的代码片段相当于:

1
2
3
4
5
6
Object.assign(  
{},
{ count: this.state.count + 1 },
{ count: this.state.count + 1 },
{ count: this.state.count + 1 },
)

Object.assign()用于将数据从源对象拷贝到目标对象。如果拷贝的源对象有相同的键,像上例这样,后面的值会覆盖前面的值。这里有一个简单的Object.assign()案例:

1
2
3
4
5
6
7
8
9
10
let count = 3

const object = Object.assign({},
{count: count + 1},
{count: count + 2},
{count: count + 3}
);

console.log(object);
// output: Object { count: 6 }

所以调用不是执行了三次,而是一次。我们可以通过传递一个函数给setState()来修复这个问题。正如你传递给setState()一个对象,同样的也可以传递函数,这是解决上述情况的办法。

我们可以这样写handleIncrement函数:

1
2
3
4
5
handleIncrement = () => {
this.setState((prevState) => ({ count: prevState.count + 1 }))
this.setState((prevState) => ({ count: prevState.count + 1 }))
this.setState((prevState) => ({ count: prevState.count + 1 }))
}

现在我们就可以通过一次点击来增加数量三次了。

在这个案例中,React不会对其进行合并,而是根据函数的顺序执行,然后在函数全部执行完之后更新状态。这会将计数更新到3而不是1.

使用更新器访问之前的状态

当我们构建React应用时,经常会遇到需要通过之前的状态来己算现在的状态。我们并不能够在总是调用setState()后,通过this.state拿到准确的状态值,结果是它(this.state)总是和屏幕上渲染的状态相同。

让我们回到计数的例子中来看看它是如何工作的。假设我们有一个每次将计数递减1的函数。函数像下面这样:

1
2
3
changeCount = () => {
this.setState({ count: this.state.count - 1})
}

我们想要的是能够减去3,changeCount()函数被绑定在点击事件上的调用了3次,像这样:

1
2
3
4
5
handleDecrement = () => {
this.changeCount()
this.changeCount()
this.changeCount()
}

每次减少的按钮被点击,计数减少了1而不是3。这是因为this.state.count直到组件被重新渲染之后才进行了更新。解决方式是使用更新器。更新器允许你访问当前状态并立即使用它来更新其他项目。所以我们的changeCount()函数应该是这个样子:

1
2
3
4
5
changeCount = () => {
this.setState((prevState) => {
return { count: prevState.count - 1}
})
}

现在我们不再依赖于this.state的结果了。count的状态彼此依赖,所以我们现在可以在每次调用changeCount()之后获取到准确的状态值。

setState()被视为异步————也就是说,不要总是期待调用setState()之后,状态就会被改变。

总结

在使用setState()时,你需要知道非常重要的几点:

  • 一定使用setState()来更新组件状态
  • setState()可以接收对象或者函数
  • 当多次更新状态时,传递一个函数
  • 不要想在调用setState()后立即能够使用this.state获取准确的状态,代替的使用更新器来完成。

原文连接

https://css-tricks.com/understanding-react-setstate/?utm_source=ponyfoo+weekly&utm_medium=email&utm_campaign=113