Управление состоянием с помощью Unstated Next
Состояние является основополагающей частью приложений React, что означает, что управление состоянием чрезвычайно важно. С момента появления Context API, а затем и хуков управление состоянием было относительно простым, поскольку Context API помогает нам избавиться от стресса, связанного с использованием Redux.
В результате было опубликовано несколько библиотек управления состояниями для дальнейшего упрощения управления состояниями с помощью Context API, и в этой статье я расскажу о Unstated Next.
Что за Unstated Next?
Unstated Next - это библиотека, основанная на React Context API, которая позволяет глобально использовать состояние в наших приложениях. Unstated Next имеет простые методы API, которые можно использовать как методы Hooks или компонента. В последующих разделах я расскажу о методах API, а затем создам простую демонстрацию, чтобы продемонстрировать, как работает Unstated Next.
Unstated Next может быть установлен из Yarn или npm:
yarn add unstated-next
// or
npm install unstated-next
Какие проблемы решает Unstated Next?
Поскольку Unstated Next построен на Context API, он решает те же проблемы, что и Context API. Unstated Next позволяет нам:
- Доступ к родительскому состоянию из дочерних компонентов
- Избегать сверления опор
Если вы не знакомы с Context API, желательно прочитать эту статью, прежде чем продолжить.
Unstated Next API методы
Unstated Next содержит три метода API, и я кратко расскажу о каждом из них:
createContainer(ourHook)
Метод createContainer
используется для инициализации контекста и принимает в качестве аргумента Hook, состояние которого должно использоваться глобально. Этот контекст традиционно присваивается переменной, как показано ниже:
import { createContainer } from 'unstated-next'
function ourHook() {}
let ourContext = createContainer(ourHook)
// ourContext == {Provider, useContainer}
Переменная, в свою очередь, возвращает провайдера и метод useContainer
. Метод useContainer
также может называться как потребитель.
Метод провайдера
Метод провайдера, который добавляется к контексту, принимает необязательное начальное значение, которое будет отображаться при первом рендеринге приложения, и принимает дочерний компонент, который будет отображаться. Вот пример его использования:
import { createContainer } from 'unstated-next'
...
function Component() {}
<OurContainer>
<Component />
</Ourcontainer>
<OurContainer initialState={"initial value"} />
<Component />
</Ourcontainer>
useContainer(ctx)
Hook useContainer(ctx)
принимает в качестве аргумента контекстную переменную и хранится в переменной, которая будет использоваться для доступа к глобальному состоянию. Hook useContainer(ctx)
используется в компоненте, где состояние должны быть доступно - в этом случае дочерний компонент. Это будет продемонстрировано в следующем разделе.
import { useContainer } from "unstated-next"
function ChildComponent() {
let activity = useContainer(OurContainer)
return <input value={activity.value} onChange={activity.onChange} />
}
Выше представлены доступные методы API в библиотеках Unstated Next, которые построены исключительно на React API. В следующем разделе я буду создавать простое приложение, чтобы продемонстрировать, как работают методы.
Настройка
Прежде чем мы углубимся в глубину, давайте наметим структуру проекта и установим зависимости, необходимые для нашего приложения. Начнем с создания папки нашего проекта.
mkdir unstated-todo-app && cd unstated-todo-app
mkdir public src src/components
cd public && touch index.html style.css
cd ../src && touch index.js
cd components && touch Todos.js
Далее мы инициализируем каталог и установим необходимые зависимости.
npm init -y
npm i react react-dom react-scripts unstated-next
Теперь пришло время записать компонент рендеринга App
в файл index.js.
index.js
Этот файл содержит компонент, отвечающий за рендеринг нашего компонента Todo``s
. Сначала я импортирую необходимые зависимости:
import React from "react";
import { render } from "react-dom";
import TodoList from "./Components/Todos";
Не волнуйтесь, компонент TodoList
будет построен после этого. Далее мы сообщаем React визуализировать наше приложение на узле div с идентификатором "root"
:
function App() {
return (
<>
<h3> Unstated Todo App </h3>
<hr />
<TodoList />
</>
)
}
render(<App />, document.getElementById("root"))
Далее мы начнем писать компонент Todo``s
.
Todos.js
Компонент Todos
содержит пользовательский хук, который будет обрабатывать состояние для нашего приложения наряду с некоторыми методами состояния и компонентом, который позволяет нам добавлять и визуализировать наши задачи.
Мы начнем с создания нашего пользовательского хука и инициализации двумя объектами состояния:
import React, { useState } from "react";
function useTodos(initialstate = [{todo: "Test todo"}]) {
let [todos, setTodo] = useState(initialstate)
let [todo, setTodoItem] = useState("")
Наш Hook useTodos()
принимает первоначальный список дел (который является необязательным), который отображается при загрузке приложения. У него два состояния объекта: todos
и todo
. Объект состояния todos
- это массив всех задач в нашем приложении, в то время как объект состояния todo
- это задача, добавляемая в массив todos
и имеющая начальное значение пустой строки.
Значение todo
устанавливается из пользовательского ввода, а затем добавляется в массив todos
с помощью метода setTodo()
, который мы рассмотрим далее.
...
const handleInput = e => {
setTodoItem(e.target.value)
}
const addTodo = e => {
e.preventDefault()
setTodo([...todos, {todo}])
setTodoItem("")
}
const removeTodo = id => {
const todoList = todos.filter(todo => todo.todo !== id)
return setTodo(todoList)
}
return { todos, todo, addTodo, removeTodo, handleInput }
}
- Метод
handleInput()
используется для установки состоянияtodo
к значению входов пользователя в форме, используя обработчик состоянияsetTodoItem
. - Метод
addTodo()
добавляет элемент to-do в массивtodos
с помощью методаsetTodo([...todos, todo])
. Затем он устанавливает состояниеtodo
в пустую строку. - Метод
removeTodo()
удаляет to-do из массиваtodos
.
Затем мы возвращаем значения состояния и метода Хука, чтобы он был доступен в наших компонентах.
Далее мы создадим контейнер из нашего хука useTodo()
. Во-первых, мы импортируем хук createContainer
из Unstated. Далее:
// After the react import
import { createContainer } from "unstated-next";
Затем мы создаем контейнер, который будет использоваться в наших компонентах. Это дает нам прямой доступ к состоянию нашего приложения и его методу, как обсуждалось в предыдущем разделе:
let Todos = createContainer(useTodos)
Контейнер еще ничего не делает, и мы не создали компоненты для задачи. Вы правильно догадались - мы будем строить это дальше.
function Todo({ todo }) {
let todosContainer = Todos.useContainer()
return (
<>
<ul className="w3-ul w3-card-4">
<li key={todo.todo} className="w3-display-container" >
{todo.todo}
<span className="w3-button w3-transparent w3-display-right" onClick={() => todosContainer.removeTodo(todo.todo)}>×</span>
</li>
</ul>
</>
)
}
Компонент выше отвечает за рендеринг todo
переданного в качестве реквизита из итерации массива todos
.
Далее мы создаем потребительский компонент, который отображает задачи и позволяет добавить задачу:
function DisplayTodos() {
let todosContainer = Todos.useContainer()
return (
<React.Fragment>
<input type="text" className="w3-input w3-border w3-round" placeholder="Write an article" value={todosContainer.todo} onChange={todosContainer.handleInput} />
<button onClick={todosContainer.addTodo} className="w3-button w3-round w3-black">Add Todo</button>
<hr />
{
todosContainer.todos.map(todo => (
<Todo todo={todo} />
))
}
</React.Fragment>
)
}
В строках 8–12 мы перебираем состояние массива todos
, а затем передаем каждый из них todo
в качестве опоры компоненту Todo
.
В вышеприведенных компонентах контейнер используется из переменной todosContainer
, чтобы обеспечить доступ к состояниям и его методам, что можно видеть в блоках рендеринга каждого компонента.
Далее мы определяем компонент, который отображает потребительский компонент DisplayTodos()
под поставщиком контекста, так как ничто не может быть потреблено без поставщика.
export default function TodoList() {
return (
<Todos.Provider>
<DisplayTodos />
</Todos.Provider>
)
}
Мы делаем компонент экспортом по умолчанию, так как он был импортирован для визуализации в компоненте App
.
Запуск нашего приложения
С завершением процесса сборки нашего приложения, мы еще написать код для index.html
и style.css
файлов. Давайте сделаем это, прежде чем мы продолжим:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link rel="stylesheet" href="style.css" type="text/css">
<title>Unstated-next Recipe App</title>
</head>
<body>
<div id="root" class="w3-container"></div>
</body>
</html>
style.css
body {
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
background-color: antiquewhite;
}
После этого давайте настроим package.json
, чтобы мы могли запустить наше приложение. Под разделом scripts
замените код там на:
"start": "react-scripts start"
Сделав это, давайте запустим приложение и посмотрим на него вживую http://localhost:3000:
npm run start
Вот демонстрация используемого приложения:
Вывод
Эта статья должна дать вам общее представление о том, что такое Unstated Next, его функциях и как оно работает. Основной вывод заключается в том, что вы можете использовать Unstated Next вместо традиционных React Context API Hooks и Redux.
Unstated Next - отличная библиотека React для управления приложениями состояния. Однако следует помнить, что существуют случаи, когда не следует использовать Context API и, следовательно, Unstated Next. Читайте о них в этой статье. Код, используемый в этой статье, можно найти на GitHub.