使用 Unstated-Next 在 React 中管理状态

Avatar of Kingsley Silas
Kingsley Silas

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

之前的文章 中,我们了解了如何使用 Unstated 来管理状态。您可能还记得,Unstated 使用 React 内置的 setState 允许您创建可以通过订阅提供者来使用状态的组件——就像 React 的 Context API

现在,我们将基于上一篇文章,探讨 Unstated Next,这是一个由作者 Jamie Kyle 认定为其 Unstated 项目“精神继承者”的库。Unstated Next 提供了 React HooksContext API 来管理状态。在 React Hooks 成为成熟功能之前,Unstated 是对 React Hooks 概念的最小抽象。但现在 React 中的 Hooks 非常出色,这种抽象变得不必要了,Unstated Next 只是将它们整合进来,同时提供了一个 API 来使用 Hooks 共享状态和逻辑。

我们将专门研究如何使用 Unstated Next 在单个和多个组件中管理状态。在继续之前,查看 关于 Unstated 的上一篇文章 可能会有所帮助,但这并不是绝对必要的。

示例:一个最小的表单组件

首先,我们将为一个表单创建一个微型的 React 应用程序,该表单仅包含一个用于输入人员姓名的文本输入框和一个提交按钮。当单击按钮时,我们将姓名显示为表单上方的段落元素。此示例的源代码可在 GitHub 上找到。

这将是一个 Bootstrap React 应用程序,我们可以使用 Create React App 启动它。让我们安装它,然后更改目录进入项目文件夹。

npx create-react-app unstated-next-form
cd unstated-next-form>

我们需要添加 Unstated Next 作为依赖项

## yarn
yarn add unstated-next

## npm
npm install --save unstated-next

我们将使用 React Hooks 和 Unstated Next 中的 createContainer,因此让我们将它们导入 App 组件

// src/App.js
import React, { useState } from 'react';
import { createContainer } from "unstated-next";

接下来,我们将创建一个自定义钩子。我们将把我们的状态放在里面,可以使用 useState 创建它

// src/App.js
// ...same as before

const useForm = () => {
  const [input, setValue] = useState("");
  const [name, setName] = useState("Barney Stinson");

  const handleInput = event => {
    setValue(event.target.value);
  };

  const updateName = event => {
    event.preventDefault();
    setName(input);
    setValue("");
  };

  return {
    input,
    name,
    handleInput,
    updateName,
  };
};

这里定义了两个状态。input 用于跟踪输入到文本输入框中的值,它将使用 handleInput 方法更新。name 将在单击按钮时更新,这将触发 updateName 方法。

好的,现在我们可以通过将我们的自定义钩子作为参数传递给 createContainer() 方法来创建一个容器。

// src/App.js
// ...same as before

const FormContainer = createContainer(useForm);

这将创建一个容器,我们可以在整个应用程序中使用它。是的,您没看错,但让我们一步一步来。我们从这个组件开始,看看它如何在 Unstated Next 中工作。

现在,让我们创建一个看起来像这样的 Form 组件。

// src/App.js
// ...same as before

const Form = () => {
  const form = FormContainer.useContainer();
  return (
    <div>
      <p>Hello! {form.name}</p>
      <div>
        <input
          type="text"
          value={form.input}
          onChange={form.handleInput}
        />
        <button onClick={form.updateName}>Save</button>
      </div>
    </div>
  );
};

我们将变量 form 赋值给调用 FormContainer.useContainer() 获得的值。该值包含我们在上面创建的自定义钩子中定义的状态和方法。有了它,我们就可以使用提供给我们的状态和方法了——但要做到这一点,我们必须将 Form 组件包装在一个提供者中。

const App = () => (
  <Form.Provider>
    <Form />
  </Form.Provider>
)

根据您到目前为止学到的知识,尝试使用 Unstated Next 构建一个最小的待办事项应用程序。如果您遇到困难,请随时查看此 仓库,了解我如何构建我的应用程序。

示例:在多个组件之间共享状态

好的,您之前得到提示,我们可以根据需要在任何地方使用我们的表单容器。使用 Unstated Next 的好处之一是它可以跨多个组件共享状态。为了了解其工作原理,我们将构建一个小型应用程序,该应用程序使用我们在上面创建的表单功能,还可以使用相同的状态创建待办事项。用户姓名可以在表单组件中更新,此更新也会反映在待办事项组件中。一石二鸟!

此示例也有一个 仓库,因此,当我们继续前进时,可以随时克隆或下载它。

让我们启动一个新项目并安装必要的依赖项

npx create-react-app unstated-next-app
cd unstated-next-app
yarn unstated-next shortid

应用程序的状态将位于一个单独的文件中。我们希望在存储中拥有表单和待办事项组件的状态,以及更新它们所需的方法。在 src 目录中创建一个名为 store.js 的文件,并使其看起来像这样:

// src/store.js
import { useState } from "react";
import shortid from "shortid"
import { createContainer } from 'unstated-next'
export const useStore = () => {
  // Construct a list that contains two default tasks
  const list = [
    { id: 1, title: 'Write code' },
    { id: 2, title: 'Buy milk' }
  ]
  const [input, setValue] = useState("");
  // Let's set a legen -- wait for it -- dary default name that updates on form submit
  const [name, setName] = useState("Barney Stinson");
  const [todos, addTodo] = useState(list);
  const [item, setTodo] = useState("");
  const handleInput = event => {
    setValue(event.target.value);
  };
  const updateName = event => {
    event.preventDefault();
    setName(input);
    setValue("");
  };
  const handleTodo = event => {
    setTodo(event.target.value);
  };
  const handleSubmit = event => {
    event.preventDefault();
    const value = {
      id: shortid.generate(),
      title: item
    }
    addTodo(todos.concat(value));
    setTodo("");
  };
  return {
    input,
    name,
    handleInput,
    updateName,
    todos,
    item,
    handleTodo,
    handleSubmit
  };
}
export const StoreContainer = createContainer(useStore)

我们使用 useState() 创建所需的状态。方法被定义,所有这些都发生在自定义钩子 useStore() 内部。我们创建 StoreContainer,然后将 useStore() 作为参数传递给 createContainer()。有了它,我们就可以在需要使用我们定义的状态和方法的组件中使用 StoreContainer 了。

从表单部分开始,创建一个名为 form.js 的文件,它应该如下所示:

// src/form.js
import React from "react";
import { StoreContainer} from "./store";

const FormComponent = () => {
  const form = StoreContainer.useContainer();
  return (
    <div>
      <p>Hello! {form.name}</p>
      <div>
        <input type="text" value={form.input} onChange={form.handleInput} />
        <button onClick={form.updateName}>Change Name</button>
      </div>
    </div>
  );
};
export default FormComponent;

我们使用 StoreContainer 访问我们需要的状态和方法。我们将对任务组件执行相同的操作,您可以在 todo.js 文件中创建它。

// src/todo.js
import React from "react";
import { StoreContainer } from "./store";

const TodoComponent = () => {
  const todo = StoreContainer.useContainer();
  return (
    <div>
      <p>Add Todos</p>
      <input type="text" value={todo.item} onChange={todo.handleTodo} />
      <button onClick={todo.handleSubmit}>Add</button>
      <div>
        <p>Dear {todo.name}, here are your current tasks;</p>
        {todo.todos.map((item) => {
          return (
            <ul key={item.id}>
              <li>{item.title}</li>
            </ul>
          );
        })}
      </div>
    </div>
  );
};
export default TodoComponent;

您可以看到 todo.name 只能在 FormComponent 中更新。这是因为我们需要一种方法在两个组件中提供状态。这就是我们将再次转向 Provider 并像在前面的示例中那样在 App 组件中添加一个的原因。

import React from 'react';
import TodoComponent from "./todo";
import FormComponent from "./form";
import { StoreContainer } from "./store"

function App() {
  return (
    <div className="App">
      <StoreContainer.Provider>
        <FormContainer />
        <TodoContainer />
      </StoreContainer.Provider>
    </div>
  );
}
export default App;

就是这样!通过添加提供者,可以从表单组件获取数据,将其存储在提供者中,然后传递回任务列表。💥