Understanding React `setState` 翻译

707 阅读6分钟

了解React中的setState

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

setState的工作机制

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

import React, { Component } from 'react'

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

    state = {
      searchTerm: ''
    }
  }
}

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

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指示搜索组件更新值然后将搜索项进行合并。

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

// old
<div>
  <Search />
</div>

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

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

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

// do not do this
this.state = {
  searchTerm: event.target.value
}

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

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

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

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():

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 })
}

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

上面的代码片段相当于:

Object.assign(  
  {},
  { count: this.state.count + 1 },
  { count: this.state.count + 1 },
  { count: this.state.count + 1 },
)

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

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函数:

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的函数。函数像下面这样:

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

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

handleDecrement = () => {
  this.changeCount()
  this.changeCount()
  this.changeCount()
}

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

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