Главная » Статьи » Управление состоянием в React с помощью Unstated

Управление состоянием в React с помощью Unstated

Управление состоянием в React с помощью Unstated

От автора: по мере усложнения вашего приложения управление состоянием может стать утомительным. В React состояния компонента должны быть самодостаточными, что делает затруднительным передачу состояния между несколькими компонентами. Redux представляет собой передовую библиотеку для управления состоянием в React, однако, в зависимости от того, насколько сложна ваша программа, вам может Redux и не понадобится.

Unstated — это альтернатива, которая предоставляет возможность управлять состоянием нескольких компонентов с помощью компонентов класса Container, а также компонентов Provider и Subscription. Давайте рассмотрим Unstated в действии, создав простой счетчик, а затем разработаем более продвинутое приложение.

Использование Unstated для создания счетчика

Код для счетчика, который мы создадим, доступен на GitHub.

Вы можете добавить Unstated в свое приложение с помощью Yarn:

yarn add unstated

Container

Контейнер расширяет класс 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

Мы определили Container (CounterContainer), установили для его начального состояния count с нулевым значением и определенные методы добавления и вычитания из состояния компонента для увеличения или уменьшения значения на 1.
Возможно, вам интересно, почему мы не импортировали в этот момент React. Нет необходимости импортировать его в контейнер, поскольку мы вообще не будем обрабатывать JSX.

Эмиттеры событий будут использоваться для вызова setState() и обеспечения повторного рендеринга компонентов. Компоненты, которые будут использовать этот контейнер, должны будут подписаться на него.

Subscribe

Компонент Subscribe используется для подключения состояния к компонентам, которые в нем нуждаются. Отсюда мы сможем вызвать методы увеличения и уменьшения на 1, которые будут обновлять состояние приложения и заставлять подписанный компонент повторно отображаться с корректным числом. Эти методы будут инициироваться несколькими кнопками, которые содержат прослушиватели событий для добавления или вычитания, соответственно.

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 присваивается CounterContainer в виде массива для его свойства to. Это означает, что компонент Subscribe может подписаться на несколько контейнеров, и все контейнеры передаются в свойство to компонента Subscribe в массиве.

counterContainer — это функция, которая получает экземпляр каждого контейнера, на который подписывается компонент Subscribe. При этом мы можем теперь получить доступ к состоянию и методам, доступным в контейнере.

Provider

Мы будем использовать компонент 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 позволяет вам использовать весь функционал, предоставляемый setState() React. Например, если мы хотим увеличивать предыдущее состояние на три одним кликом мыши, мы можем передать функцию 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, которое представляет собой массив с двумя элементами. Чтобы добавить элементы «to-do», у нас есть состояние todo, равное пустой строке.

Нам понадобится компонент CreateTodo, который будет подписываться на контейнер. Каждый раз, когда вводится значение, запускается событие onChange, а затем запускает метод handleInputChange(), который содержится в контейнере. Нажатие кнопки отправки вызовет handleAddTodo(). Метод handleDeleteTodo() получает задачу и отфильтровывает to-do, которые соответствуют переданной задаче.

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

Этот компонент перебирает массив to-dos, доступных в контейнере, и отображает их в списке. Наконец, нам нужно обернуть компоненты, которые подписываются на контейнер в провайдере, как мы это делали в случае счетчика. Мы делаем это в файле 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, хотя и является удивительным, не всегда лучший инструмент для работы, хотя мы часто прибегаем к нему в подобных случаях. Надеюсь, теперь вы чувствуете, что у вас есть новый инструмент в арсенале.

Автор: Kingsley Silas

Источник: https://css-tricks.com/

Редакция: Команда webformyself.