在 之前的文章 中,我们了解了如何使用 Unstated 来管理状态。您可能还记得,Unstated 使用 React 内置的 setState
允许您创建可以通过订阅提供者来使用状态的组件——就像 React 的 Context API。
现在,我们将基于上一篇文章,探讨 Unstated Next,这是一个由作者 Jamie Kyle 认定为其 Unstated 项目“精神继承者”的库。Unstated Next 提供了 React Hooks 和 Context 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;
就是这样!通过添加提供者,可以从表单组件获取数据,将其存储在提供者中,然后传递回任务列表。💥
嗨,我是 Jamie Kyle
感谢您撰写关于 Unstated Next 的文章。非常感谢。
不过,我还是要指出,我不建议使用共享的
StoreContainer
,我建议使用单独的TodoContainer
和FormContainer
。容器应该只承担一项责任。如果您试图用 Unstated Next 重建 Redux,您将遇到麻烦。我建议不要为所有内容都使用容器。大多数状态可以通过一些自定义 Hooks 来实现。只有当您确实希望拥有共享状态和 API 时,才应该使用容器。
干杯!
嗨,感谢这篇文章!
我发现我需要更改
<Form.Provider>
为
<FormContainer.Provider>
才能使此示例正常工作。
干杯
感谢这篇文章,它很新颖,而且正是我现在在思考的问题。我正在寻求使用 Unstated-next,并且希望将其与真实的数据获取结合使用。待办事项应用程序很棒,但在状态管理方面,很多数据都包含在其中,本教程没有涵盖这些内容。网上也没有任何示例,一个都没有。如果我在 useForms 的状态中有一个名为 names 的状态片段,它最初为空数组,然后在其中我创建一个名为 updateNames 的函数,类似于 updateName,并在其中我使用自定义的异步 useFetch 钩子“从 API 获取所有名称列表”,然后用此名称数组更新 names 的状态片段,例如,我得到一个“无效的钩子调用。钩子只能在函数组件的主体中调用”的错误。当获取和存储数据时,如何使用 Unstated-next,这实际上是存储的最大好处之一。我希望您能尽快收到并回复此问题!你们非常乐于助人,我将不胜感激。干杯!