使用 Unstated 管理 React 中的状态

Avatar of Kingsley Silas
Kingsley Silas

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

随着应用程序变得越来越复杂,状态管理可能会变得很繁琐。组件的状态应该自包含,这使得跨多个组件共享状态成为一个难题。Redux 通常是用于管理 React 中状态的首选库,但是,根据应用程序的复杂程度,您可能不需要 Redux

Unstated 是一种替代方案,它允许您使用容器类以及 Provider 和 Subscribe 组件跨多个组件管理状态。让我们通过创建一个简单的计数器来了解 Unstated 的实际应用,然后看看更高级的待办事项应用程序。

使用 Unstated 创建计数器

我们正在创建的计数器的代码可在 GitHub 上获取

查看代码库

您可以使用 Yarn 将 Unstated 添加到您的应用程序中

yarn add unstated

容器

容器扩展了 Unstated 的 Container 类。它仅用于状态管理。在这里,将初始化初始状态,并且将发生对 setState() 的调用

import { Container } from 'unstated'

class CounterContainer extends Container {
  state = {
    count: 0
  }

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

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

export default CounterContainer

到目前为止,我们已经定义了容器(CounterContainer),将其初始状态设置为 count 为零,并定义了以 1 为增量和减量对组件状态进行加减的方法。

您可能想知道为什么我们此时没有导入 React。无需将其导入容器,因为我们根本不会渲染 JSX。

将使用事件发射器来调用 setState() 并导致组件重新渲染。将使用此容器的组件必须订阅它。

订阅

Subscribe 组件用于将状态插入需要它的组件中。从这里,我们将能够调用增量和减量方法,这些方法将更新应用程序的状态并导致订阅的组件使用正确的计数重新渲染。这些方法将由几个包含事件侦听器的按钮触发,以分别对计数进行加减。

import React from 'react'
import { Subscribe } from 'unstated'

import CounterContainer from './containers/counter'

const Counter = () => {
  return (
    <Subscribe to={[CounterContainer]}>
      {counterContainer => (
        <div>
          <div>
            // The current count value
            Count: { counterContainer.state.count }
          </div>
          // This button will add to the count
          <button onClick={counterContainer.increment}>Increment</button>
          // This button will subtract from the count
          <button onClick={counterContainer.decrement}>Decrement</button>
        </div>
      )}
    </Subscribe>
  )
}

export default Counter

Subscribe 组件以数组的形式将其 to 属性设置为 CounterContainer。这意味着 Subscribe 组件可以订阅多个容器,并且所有容器都作为数组传递给 Subscribe 组件的 to 属性。

counterContainer 是一个函数,它接收 Subscribe 组件订阅的每个容器的实例。

这样,我们现在可以访问容器中可用的状态和方法。

提供者

我们将使用 Provider 组件来存储容器实例并允许子组件订阅它。

import React, { Component } from 'react';
import { Provider } from 'unstated'

import Counter from './Counter'

class App extends Component {
  render() {
    return (
      <Provider>
        <Counter />
      </Provider>
    );
  }
}

export default App;

这样,Counter 组件就可以使用我们的 counterContainer 了。

Unstated 允许您使用 React 的 setState() 提供的所有功能。例如,如果我们想通过一次点击将先前状态增加三次,我们可以像这样将函数传递给 setState()

incrementBy3 = () => {
  this.setState((prevState) => ({ count: prevState.count + 1 }))
  this.setState((prevState) => ({ count: prevState.count + 1 }))
  this.setState((prevState) => ({ count: prevState.count + 1 }))
}

其思想是 setState() 仍然像以前一样工作,但这次能够将状态保存在 Container 类中。它可以轻松地将状态传播到仅需要它的组件。

让我们创建一个待办事项应用程序!

这是 Unstated 的稍微高级一点的用法。两个组件将订阅容器,该容器将管理所有状态以及更新状态的方法。同样,代码可在 Github 上获取

查看代码库

容器将如下所示

import { Container } from 'unstated'

class TodoContainer extends Container {
  state = {
    todos: [
      'Mess around with unstated',
      'Start dance class'
    ],
    todo: ''
  };

  handleDeleteTodo = (todo) => {
    this.setState({
      todos: this.state.todos.filter(c => c !== todo)
    })
  }
 
  handleInputChange = (event) => {
    const todo = event.target.value
    this.setState({ todo });
  };

  handleAddTodo = (event) => {
    event.preventDefault()
    this.setState(({todos}) => ({
      todos: todos.concat(this.state.todo)
    }))
    this.setState({ todo: '' });
  }

}

export default TodoContainer

容器具有一个初始 todos 状态,它是一个包含两个项目的数组。要添加待办事项,我们有一个 todo 状态设置为空字符串。

我们将需要一个 CreateTodo 组件来订阅容器。每次输入值时,onChange 事件都会触发,然后触发我们在容器中拥有的 handleInputChange() 方法。单击提交按钮将触发 handleAddTodo()handleDeleteTodo() 方法接收一个待办事项并过滤掉与传递给它的待办事项匹配的待办事项。

import React from 'react'
import { Subscribe } from 'unstated'

import TodoContainer from './containers/todoContainer'

const CreateTodo = () => {
  return (
    <div>
      <Subscribe to={[TodoContainer]}>
        {todos =>
          <div>
            <form onSubmit={todos.handleAddTodo}>
              <input
                type="text"
                value={todos.state.todo}
                onChange={todos.handleInputChange}
              />
              <button>Submit</button>
            </form>
          </div>
        }
      </Subscribe>
    </div>
  );
}

export default CreateTodo

添加新的待办事项时,容器中可用的 todos 状态将更新。待办事项列表从容器中提取到 Todos 组件,方法是将组件订阅到容器。

import React from 'react';
import { Subscribe } from 'unstated';

import TodoContainer from './containers/todoContainer'

const Todos = () => (
  <ul>
    <Subscribe to={[TodoContainer]}>
      {todos =>
        todos.state.todos.map(todo => (
          <li key={todo}>
            {todo} <button onClick={() => todos.handleDeleteTodo(todo)}>X</button>
          </li>
        ))
      }
    </Subscribe>
  </ul>
);

export default Todos

此组件遍历容器中可用的待办事项数组并在列表中渲染它们。

最后,我们需要像在计数器示例中一样,将订阅容器的组件包装在提供者中。我们完全像在计数器示例中所做的那样,在我们的 App.js 文件中执行此操作

import React, { Component } from 'react';
import { Provider } from 'unstated'

import CreateTodo from './CreateTodo'
import Todos from './Todos'

class App extends Component {
  render() {
    return (
      <Provider>
        <CreateTodo />
        <Todos />
      </Provider>
    );
  }
}

export default App;

总结

根据应用程序的复杂程度,有不同的方法可以在 React 中 管理状态,而 Unstated 是一个方便的库,可以简化此过程。值得重申的是,尽管我们经常在这些情况下使用 Redux,但它虽然很棒,但并非总是最佳工具。希望您现在感觉自己又掌握了一项新技能。