От автора: по мере усложнения вашего приложения управление состоянием может стать утомительным. В 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.