使用 Unstated 在 React 中轻松管理状态

另一天,在React!中管理应用程序状态的另一种方法是(https://github.com/jamiebuilds/unstated)使用React的新 背景API来管理您的状态的新库。

让我们来看看如何使用Unstated,不要担心,它将是完全无痛的!

安装

只需使用 npm 或 Yarn 安装未注明的包:

1$ npm install unstated
2
3# or
4$ yarn add unstated

使用

Unstated为我们提供了三种内容:一个集装箱类、一个订阅组件和一个提供商组件:

集装箱

您可以通过扩展 Unstated 的集装箱类来创建您的状态的一块集装箱:

1[label ./containers/todo.js]
2import { Container } from 'unstated';
3
4class TodoContainer extends Container {
5  // ...
6}
7
8export default TodoContainer;

在容器中,您可以使用状态和 setState 来管理状态,正如您在 React 中对本地组件状态所做的那样:

 1[label ./containers/todo.js]
 2import { Container } from 'unstated';
 3
 4class TodoContainer extends Container {
 5  state = {
 6    todos: []
 7  };
 8
 9  id = 0;
10
11  addTodo = todo => {
12    const newTodo = { id: this.id++, marked: false, description: todo };
13    this.setState({
14      todos: [...this.state.todos, newTodo]
15    });
16  };
17
18  removeTodo = id => {
19    this.setState({
20      todos: this.state.todos.filter(todo => todo.id !== id)
21    });
22  };
23
24  markTodo = id => {
25    this.setState({
26      todos: this.state.todos.map(todo => {
27        if (todo.id !== id) {
28          return todo;
29        } else {
30          return { ...todo, marked: !todo.marked };
31        }
32      })
33    });
34  };
35}
36
37export default TodoContainer;

正如你所看到的,我们所有的商业逻辑都包含在集装箱内。我们的React组件将能够从这个集装箱中订阅状态,并通过调用集装箱中定义的方法来更改状态。

供应商

提供者组件用于存储容器实例,并允许其子女订阅这些实例. 只需在顶级使用它,围绕将订阅容器的组件:

 1[label App.js]
 2import React, { Component } from 'react';
 3import { Provider } from 'unstated';
 4
 5import Todos from './Todos';
 6import AddTodo from './AddTodo';
 7
 8class App extends Component {
 9  render() {
10    return (
11      <Provider>
12        <AddTodo />
13        <Todos />
14      </Provider>
15    );
16  }
17}
18
19export default App;

签名

最后,Unstated的订阅组件采用一个集合集装箱的组件,并预期一个作为其子女的组件的组件(见 render prop)将收到每个订阅的集装箱的实例。

用我们的例子,我们的 AddTodo 组件可以看起来像这样:

 1[label AddTodo.js]
 2import React from 'react';
 3import { Subscribe } from 'unstated';
 4
 5import TodoContainer from './containers/todo';
 6
 7class AddTodo extends React.Component {
 8  inputRef = React.createRef();
 9
10  handleClick = addTodo => {
11    if (this.inputRef.current.value) {
12      addTodo(this.inputRef.current.value);
13      this.inputRef.current.value = '';
14    }
15  };
16
17  render() {
18    return (
19      <div>
20        <input type="text" placeholder="your new todo" ref={this.inputRef} />
21
22        <Subscribe to={[TodoContainer]}>
23          {todoContainer => (
24            <button onClick={() => this.handleClick(todoContainer.addTodo)}>
25              Add
26            </button>
27          )}
28        </Subscribe>
29      </div>
30    );
31  }
32}
33
34export default AddTodo;

注意如何访问一个Todo容器实例,并可以调用它的addTodo方法. 在这里,我们还使用React的 新创建Ref API


让我们在我们的 Todos 组件中使用 Unstated 的订阅组件来订阅我们的 todo 容器,显示包含在其状态中的 todos,并允许将其标记为完成或删除它们:

 1[label Todos.js]
 2import React from 'react';
 3import { Subscribe } from 'unstated';
 4
 5import TodoContainer from './containers/todo';
 6
 7class Todos extends React.Component {
 8  render() {
 9    return (
10      <ul>
11        <Subscribe to={[TodoContainer]}>
12          {todoContainer =>
13            todoContainer.state.todos.map(todo => (
14              <li key={todo.id}>
15                <span
16                  className={todo.marked ? 'marked' : null}
17                  onClick={() => todoContainer.markTodo(todo.id)}
18                >
19                  {todo.description}
20                </span>
21                <button onClick={() => todoContainer.removeTodo(todo.id)}>
22                  X
23                </button>
24              </li>
25            ))
26          }
27        </Subscribe>
28      </ul>
29    );
30  }
31}
32
33export default Todos;

多种集装箱

将不同的状态分离成自己的单独容器同样容易。比如说,我们的应用现在也需要处理任务,让我们把我们的 AddTodo 组件重构成一个 AddItem 组件,该组件也订阅到 TaskContainer。

我们将使用几个无线电按钮让用户选择添加一个Todo或一个任务,我们还将更改我们的文本输入元素为一个控制的输入,而不是使用一个ref:

 1[label AddItem.js]
 2import React from 'react';
 3import { Subscribe } from 'unstated';
 4
 5import TodoContainer from './containers/todo';
 6import TaskContainer from './containers/task';
 7
 8class AddItem extends React.Component {
 9  state = {
10    itemValue: '',
11    itemChoice: 'todo'
12  };
13
14  handleInputChange = e => {
15    this.setState({
16      itemValue: e.target.value
17    });
18  };
19
20  handleRadioChange = e => {
21    this.setState({
22      itemChoice: e.target.value
23    });
24  };
25
26  handleClick = (addTodo, addTask) => {
27    if (this.state.itemValue && this.state.itemChoice === 'todo') {
28      addTodo(this.state.itemValue);
29    } else if (this.state.itemValue) {
30      addTask(this.state.itemValue);
31    }
32
33    this.setState({
34      itemValue: ''
35    });
36  };
37
38  render() {
39    return (
40      <div>
41        <input
42          type="text"
43          placeholder="your new item"
44          value={this.state.itemValue}
45          onChange={this.handleInputChange}
46        />
47
48        <input
49          type="radio"
50          id="todoItem"
51          name="itemType"
52          value="todo"
53          checked={this.state.itemChoice === 'todo'}
54          onChange={this.handleRadioChange}
55        />
56        <label htmlFor="todoItem">Todo</label>
57
58        <input
59          type="radio"
60          id="taskItem"
61          name="itemType"
62          value="task"
63          checked={this.state.itemChoice === 'task'}
64          onChange={this.handleRadioChange}
65        />
66        <label htmlFor="taskItem">Task</label>
67
68        <Subscribe to={[TodoContainer, TaskContainer]}>
69          {(todoContainer, taskContainer) => (
70            <button
71              onClick={() =>
72                this.handleClick(todoContainer.addTodo, taskContainer.addTask)
73              }
74            >
75              Add {this.state.itemChoice}
76            </button>
77          )}
78        </Subscribe>
79      </div>
80    );
81  }
82}
83
84export default AddItem;

现在我们可以在组件树内分离显示我们的 todos和任务,Todo组件订阅TodoContainer,TaskContainer订阅TaskContainer的任务组件。

 1[label App.js]
 2// ...
 3
 4class App extends Component {
 5  render() {
 6    return (
 7      <Provider>
 8        <AddItem />
 9        <Todos />
10        <Tasks />
11      </Provider>
12    );
13  }
14}
15
16export default App;

简单的蛋糕! 现在你有另一个工具在你的武器库,当它涉及到组织和管理你的React应用程序的状态。

Published At
Categories with 技术
Tagged with
comments powered by Disqus