从零开始了解 React 状态

Avatar of Kingsley Silas
Kingsley Silas

DigitalOcean 为您的旅程的每个阶段提供云产品。立即开始使用 $200 免费信用额度!

当您开始学习 React 时,您将需要理解什么是状态。状态在 React 中非常重要,也许是您 最初考虑使用 React 的主要原因之一。让我们尝试理解什么是状态以及它是如何工作的。

什么是状态?

在 React 中,状态是一个 **普通 JavaScript 对象**,它允许您跟踪组件的数据。组件的状态可能会发生变化。组件状态的变化取决于应用程序的功能。变化可以基于用户响应、服务器端的新消息、网络响应或任何其他因素。

组件状态预计是私有的,并且由同一个组件控制。要更改组件的状态,您必须在组件内部进行更改,包括初始化和更新组件的状态。

类组件

状态只对被称为 **类组件** 的组件可用。使用类组件而不是函数组件的主要原因是类组件可以拥有状态。让我们看看它们之间的区别。函数组件是 JavaScript 函数,就像这样

const App = (props) => {
  return (
    <div>
      { props }
    </div>
  )
}

如果您的组件所需的功能像上面那样简单,那么函数组件是完美的选择。类组件看起来会比它复杂得多。

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = { username: 'johndoe' }
  }
  render() {
    const { username } = this.state
    return(
      <div>
        { username }
      </div>
    )
  }
}

在上面,我将组件的用户名状态设置为字符串。

构造函数

根据官方文档,构造函数是初始化状态的正确位置。初始化状态是通过将 this.state 设置为一个对象来完成的,就像您在上面看到的那样。请记住:**状态是一个普通的 JavaScript 对象**。App 组件的初始状态被设置为一个状态对象,它包含键 username,其值为 johndoe,使用 this.state = { username: 'johndoe' }

初始化组件状态可以像这里看到的一样复杂

constructor(props) {
  super(props)
  this.state = { 
    currentTime: 0,
    status: false, 
    btnOne: false, 
    todoList: [],
    name: 'John Doe'
  }
}

访问状态

可以在 render() 方法中访问初始化的状态,就像我在上面做的那样。

render() {
  const { username } = this.state
  return(
    <div>
      { username }
    </div>
  )
}

上面代码片段的另一种写法是

render() {
  return(
    <div>
      { this.state.username }
    </div>
  )
}

区别在于我在第一个示例中从状态中提取了用户名,但也可以写成 const status = this.state.username。由于 ES6 解构,我不必那样做。当您看到类似的东西时,不要感到困惑。重要的是要记住,当这样做时,我并没有重新分配状态。状态的初始设置是在构造函数中完成的,不应该再做一次,永远不要直接更新组件的状态。

可以使用 this.state.property-name 访问状态。不要忘记,除了您初始化状态的地方之外,下一次使用 this.state 是在您想访问状态的时候。

更新状态

更新组件状态的唯一允许方法是使用 setState()。让我们看看它是如何实际工作的。

首先,我将从创建用于更新组件用户名的调用方法开始。此方法应接收一个参数,并期望使用该参数来更新状态。

handleInputChange(username) {
  this.setState({username})
}

再一次,您可以看到我将一个对象传递给了 setState()。完成之后,我需要将此函数传递给在输入框的值发生改变时调用的事件处理程序。事件处理程序将提供触发事件的上下文,这使得可以使用 event.target.value 获取在输入框中输入的值。这是传递给 handleInputChange() 方法的参数。因此,render 方法应该像这样。

render() {
  const { username } = this.state
  return (
    <div>
      <div>
        <input 
          type="text"
          value={this.state.username}
          onChange={event => this.handleInputChange(event.target.value)}
        />
      </div>
      <p>Your username is, {username}</p>
    </div>
  )
}

每次调用 setState() 时,都会向 React 发送一个请求,使用新更新的状态来更新 DOM。有了这种思维方式,您就会理解状态更新可能会被延迟。

您的组件应该像这样:

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = { username: 'johndoe' }
  }
  handleInputChange(username) {
    this.setState({username})
  }
  render() {
    const { username } = this.state
    return (
      <div>
        <div>
          <input 
            type="text"
            value={this.state.username}
            onChange={event => this.handleInputChange(event.target.value)}
          />
        </div>
        <p>Your username is, {username}</p>
      </div>
    )
  }
}

将状态作为道具传递

状态可以作为道具从父组件传递给子组件。为了实际演示,让我们创建一个用于创建待办事项列表的新组件。此组件将有一个输入字段用于输入日常任务,并将任务作为道具传递给子组件。

尝试使用您已经学到的知识自己创建父组件。

让我们从创建组件的初始状态开始。

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = { todoList: [] }
  }
  render() {
    return()
  }
}

组件的状态将 todoList 设置为一个空数组。在 render() 方法中,我想返回一个用于提交任务的表单。

render() {
  const { todoList } = this.state
  return (
    <div>
      <h2>Enter your to-do</h2>
      <form onSubmit={this.handleSubmit}>
        <label>Todo Item</label>
        <input
          type="text"
          name="todoitem"
        />
        <button type="submit">Submit</button>
      </form>
    </div >
  )
}

每次输入新项目并点击提交按钮时,都会调用 handleSubmit 方法。此方法将用于更新组件的状态。我想要更新它的一种方法是使用 concattodoList 数组中添加新值。这样做将在 setState() 方法中设置 todoList 的值。以下是它应该是什么样子

handleSubmit = (event) => {
  event.preventDefault()
  const value = (event.target.elements.todoitem.value)
  this.setState(({todoList}) => ({
    todoList: todoList.concat(value)
  }))
}

每次点击提交按钮时都会获取事件上下文。我们使用 event.preventDefault() 来停止提交的默认操作,该操作会重新加载页面。在输入字段中输入的值被分配给一个名为 value 的变量,然后在调用 todoList.concat() 时将该变量作为参数传递。React 通过将新值添加到初始空数组来更新 todoList 的状态。此新数组成为 todoList 的当前状态。当添加另一个项目时,循环会重复。

A chart illustrating the cycle explained above.

这里的目标是将单个项目作为道具传递给子组件。在本教程中,我们将其称为 TodoItem 组件。将下面的代码片段添加到您在 render() 方法中具有的父 div 内。

<div>
  <h2>Your todo lists include:</h2>
  { todoList.map(i => <TodoItem item={i} /> )}
</div>

您正在使用 map 来遍历 todoList 数组,这意味着单个项目将作为道具传递给 TodoItem 组件。要利用这一点,您需要一个接收道具并在 DOM 上渲染它的 TodoItem 组件。我将向您展示如何使用函数组件和类组件来做到这一点。

编写为函数组件

const TodoItem = (props) => {
  return (
    <div>
      {props.item}
    </div>
  )
}

对于类组件,它将是

class TodoItem extends React.Component {
  constructor(props) {
    super(props)
  }
  render() {
    const {item} = this.props
    return (
      <div>
        {item}
      </div>
    )
  }
}

如果此组件不需要管理状态,那么最好使用函数组件。

更上一层楼

在开发 React 应用程序时,您将经常处理状态。通过上面涵盖的所有内容,您应该有信心能够深入研究 React 状态管理的更高级部分。要更深入地学习,我建议您阅读 React 的官方文档,主题为 状态和生命周期,以及 Uber 的 React 指南关于道具与状态