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

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

Согласно документации,

MobX - это "проверенная в боях" библиотека, которая делает управление состоянием простым и масштабируемым за счёт функционально-реактивного программирования (TFRP).

Одним из наиболее часто задаваемых вопросов разработчиками при создании современных приложений на React является управление состоянием. В этом руководстве вы узнаете, как использовать MobX в качестве библиотеки управления состоянием для React-приложений. Мы будем использовать её для управления состоянием, что поможет нам понять концепцию и рекомендации по использованию MobX.

Что такое MobX?

Как и другие подобные библиотеки (например, Redux, Recoil, Hook states), MobX позволяет управлять состоянием вашего приложения, но она отличается простотой и масштабируемостью.

Mobx различает следующие концепции:

  • State (состояние)
  • Actions (действия)
  • Derivations (производные)

State State - это данные, которые управляют вашими приложениями. Они содержат различные типы данных, начиная от массивов, строк, чисел и объектов, с которыми MobX позволяет вам работать. Всё, что вам нужно сделать, это убедиться, что все свойства, которые вы хотите изменить, доступны для наблюдения, чтобы MobX мог их отслеживать. Ниже приведён простой пример:

import React from "react"; import ReactDOM from "react-dom"; import { makeAutoObservable } from "mobx"; import { observer } from "mobx-react"; // Model the application state. class Timer { secondsPassed = 0; constructor() { makeAutoObservable(this); } increase() { this.secondsPassed += 1; } reset() { this.secondsPassed = 0; } } const myTimer = new Timer(); // Build a "user interface" that uses the observable state. const TimerView = observer(({ timer }) => ( <button onClick={() => timer.reset()}> Seconds passed: {timer.secondsPassed} </button> )); ReactDOM.render(<TimerView timer={myTimer} />, document.body); // Update the 'Seconds passed: X' text every second. setInterval(() => { myTimer.increase(); }, 1000);

React-компонент TimeView, обёрнутый вокруг observer , автоматически обнаружит, что рендеринг зависит от timer.secondsPassed , даже если связь явно не определена.

Каждое событие (onClick/setInterval) вызывает действие (MyTimer.increase/MyTimer.reset), которое обновляет состояние observable (MyTimer.secondsPassed). Изменения в нём точно распространяются на все вычисления и эффекты (timeView), которые зависят от внесённых правок.

Action Если state - это ваши данные, то action - это любой блок кода, который может изменять такие данные: пользовательские события, внутренние данные и т.д. Action похоже на человека, который изменяет данные в ячейке электронной таблицы. В приведённом выше коде Timer, мы можем видеть методы increase и reset, которые изменяют значение secondsPassed. Actions помогают вам структурировать ваш блок кода и предотвращают постоянное изменение состояния, когда в этом нет необходимости. Методы, которые изменяют состояние, называются actions в MobX.

Derivations Всё, что получено из состояния, известно как derivation (производная). Оно существует в разных формах, но мы рассмотрим именно эти виды производных MobX:

  • Computed Values
  • Reactions

Computed Values Это значения, которые могут быть получены из состояния с помощью чистой функции. Они будут автоматически обновляться MobX, а также приостанавливаться, когда не используются. Ниже приведён пример такого значения:

class TodoList { @observable todos = []; @computed get unfinishedTodoCount() { return this.todos.filter((todo) => !todo.finished).length; } }

Reactions Reactions (реакции) подобны computed values: они реагируют на изменения состояния, но вместо этого вызывают побочные эффекты. В React вы можете превратить функциональные компоненты без состояния в реактивные компоненты, просто добавив функцию наблюдателя. Observer преобразует компоненты функции React в вывод данных, которые они отображают. Ниже приведён пример того, как можно использовать функцию наблюдателя:

const Todos = observer(({ todos }) => ( <ul> {todos.map((todo) => ( <Todoview ... /> ))} </ul> ));

Пользовательские реакции могут быть созданы с помощью autorun, reaction или when.

//autorun// autorun(() => { console.log("Tasks left: " + todos.unfinishedTodoCount); }); //reaction// const reaction = reaction( () => todos.map((todo) => todo.title), (titles) => console.log("reaction:", titles.join(", ")) ); //when// async function x() { await when(() => that.isVisible); // etc... }

MobX можно установить с помощью любого менеджера пакетов, такого как npm, используя команду npm install -- save mobx.

Почему вам следует ознакомиться с MobX?

Основная цель MobX - улучшить управление состояниями для разработчиков и создать более простой способ управления состояниями приложений Javascript с меньшим количеством кода и шаблонов. MobX использует наблюдаемые данные, которые помогают автоматически отслеживать изменения, облегчая жизнь разработчикам.

MobX позволяет вам управлять состоянием вашего приложения вне каких-либо рамок. Это делает код несвязанным, переносимым и легко тестируемым, именно поэтому он называется UNOPINIONATED.

MobX против Redux/Recoil/HookState

В отличие от других менеджеров состояний, таких как Redux и Easy Peasy, MobX использует несколько хранилищ для обработки состояния приложения. Вы можете разделить их, чтобы все состояния приложения были в одном хранилище, как и в Redux.

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

Redux более самоуверен, что приводит к чистым функциям, но MobX выигрывает из-за своей неоспоримой особенности, когда дело доходит до масштабируемости.

Когда использовать MobX?

Хотя некоторые разработчики, как правило, путаются в том, когда использовать ту или иную библиотеку управления состоянием для того или иного проекта, давайте разберём этот аспект. Если вы хотите написать минимальный код, практически без шаблонов, загораживающих ваш обзор, или если вы пытаетесь обновить поле записи без каких-либо специальных инструментов, то MobX - это то, чем вам следует воспользоваться.

Создание React-приложения с помощью MobX.

Мы создадим приложение Todo для ведения заметок и украсим его с помощью библиотеки Framer-Motion. Мы будем использовать Mobx в качестве менеджера состояния в нашем приложении Todo.

Сначала мы настроим нашу среду, создав наше приложение react с помощью следующей команды в вашем терминале.

npx create-react-app todo-app --template typescript

Затем мы меняем наш каталог и устанавливаем необходимые зависимости перед созданием наших компонентов и состояния:

cd todo-app npm install -s mobx mobx-react-lite npm install framer-motion npm install react-icons npm start

Создание компонента Store

Мы создадим компонент store.ts в корневой папке, и используем Mobx с контекстным API React, чтобы сделать наш магазин доступным для всех компонентов.

//store.ts// import { createContext, useContext } from "react"; import todoStore from "./store/TodoStore"; const store = { todoStore: todoStore(), }; export const StoreContext = createContext(store); export const useStore = () => { return useContext<typeof store>(StoreContext); }; export default store;

Создание компонента TodoStore

TodoStore.ts содержит наш компонент состояния. Сначала мы создаем функцию TodoStore, которая возвращает makeAutoObservable (из MobX) со списком с заголовком и идентификатором.

//TodoStore.ts// import { makeAutoObservable } from "mobx"; const todoStore = () => { return makeAutoObservable({ list: [] as { title: string; id: number }[], }); }; export default todoStore;

Создание компонента TodoForm

Нам нужно будет реализовать компонент TodoForm.tsx для создания Todo-приложения.

//TodoForm.tsx// import { motion } from "framer-motion"; import { GoPlus } from "react-icons/go"; import { action } from "mobx"; import { FormEvent } from "react"; import { useStore } from "../stores"; const TodoForm = () => { const { todoStore } = useStore(); const handleSubmit = action((e: FormEvent) => { e.preventDefault(); const formData = new FormData(e.target as HTMLFormElement); const value = formData.get("title")?.toString() || ""; todoStore.list.push({ title: value, id: Date.now(), }); }); return ( <form className="addTodos" action="#" onSubmit={handleSubmit}> <input name="title" placeholder="add text" className="todo-input" /> <motion.button whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }} className="add-btn" > <GoPlus /> </motion.button> </form> ); }; export default TodoForm;

Создание компонента TodoList

Чтобы перечислить все наши входные задачи, нам нужно будет создать компонент ToDoList.tsx:

//TodoList.tsx// import { AnimatePresence } from "framer-motion"; import { observer } from "mobx-react-lite"; import { useStore } from "../stores"; import { motion } from "framer-motion"; const TodoList = () => { const { todoStore } = useStore(); return ( <motion.li whileHover={{ scale: 0.9, transition: { type: "spring", duration: 0.2 }, }} exit={{ x: "-60vw", scale: [1, 0], transition: { duration: 0.5 }, backgroundColor: "rgba(255,0,0,1)", }} className="displaytodos" > {todoStore.list.map((l) => ( <h3 className="card" key={l.id}> {l.title} </h3> ))} </motion.li> ); }; export default observer(TodoList);

Создание компонента TodoDetails

Файл TodoDetails.tsx содержит наши компоненты TodoForm и ToDoList.

//TodoDetails.tsx// import React from "react"; import TodoForm from "./TodoForm"; import TodoList from "./TodoList"; function TodoOverview() { return ( <> <TodoForm /> <TodoList /> </> ); } export default TodoOverview;

Создание компонента Main.css

Стиль нашего приложения будет добавлен при использовании следующего css-кода:

@import url("https://fonts.googleapis.com/css2?family=RocknRoll+One&display=swap"); html { line-height: 1.15; } * { box-sizing: border-box; margin: 0; padding: 0; font-family: "RocknRoll One", sans-serif; } body { background: linear-gradient( 190deg, rgb(134, 123, 205) 0%, rgb(106, 90, 171) 100% ); background-repeat: no-repeat; background-size: cover; background-attachment: fixed; color: #222; overflow: hidden; } .App { margin-top: 3rem; display: flex; flex-direction: column; } .App h1 { display: inline; text-align: center; margin-bottom: 2rem; color: #e1ebfd; text-shadow: 0 0 5px #433aa8, 3px -1px 5px #271c6c; } .addTodos { display: flex; justify-content: center; } .todo-input { min-width: 15rem; width: 40vw; max-height: 2.5rem; background-color: #e1ebfd; border: none; border-radius: 5px; padding: 0.5rem 1rem; align-self: center; } .todo-input:focus { outline: none; border: 2px solid rgb(67, 58, 168); } .add-btn { margin-left: 1rem; background-color: #271c6c; color: #e1ebfd; border-radius: 50%; border: 2px solid #e1ebfd; font-size: 1.5rem; width: 3.2rem; height: 3.2rem; cursor: pointer; box-shadow: 2px 4px 10px #271c6c; display: flex; justify-content: center; align-items: center; } .add-btn:focus { outline: none; } .displaytodos { margin-top: 3rem; display: flex; flex-direction: column; align-items: center; } .card { display: flex; flex-direction: column; text-align: center; background: rgb(180, 182, 218); background: radial-gradient( circle, hsla(237, 34%, 78%, 0.9) 0%, hsla(219, 88%, 94%, 0.9) 100% ); margin: 0 1rem 1rem 0; height: 4rem; width: 18rem; border-radius: 0.5rem; padding: 1rem; position: relative; } @media Screen and (max-width: 640px) { .displaytodos { overflow: hidden; margin-top: 2rem; } .displaytodos ul { display: flex; flex-direction: column; align-items: center; margin-left: 0; align-self: center; } .card { margin-right: 0; } }

Добавление Framer-Motion

Добавление библиотеки Framer-Motion (для анимации, управляемой компонентами движения) в App.tsx нуждается в этом коде:

import React from "react"; import TodoDetails from "./components/TodoDetails"; import "./css/main.css"; import { motion } from "framer-motion"; function App() { return ( <div className="App"> <motion.h1 initial={{ y: -200 }} animate={{ y: 0 }} transition={{ type: "spring", duration: 0.5 }} whileHover={{ scale: 1.1 }} > Todo App </motion.h1> <motion.div initial={{ y: 1000 }} animate={{ y: 0 }} transition={{ type: "spring", duration: 1 }} > <TodoDetails /> </motion.div> </div> ); } export default App;
И наше приложение Todo, похоже, работает очень хорошо, обрабатывая своё внутреннее состояние с помощью MobX
И наше приложение Todo, похоже, работает очень хорошо, обрабатывая своё внутреннее состояние с помощью MobX

Заключение

В этой статье мы провели экскурсию по MobX как библиотеке управления состоянием React. Мы также узнали, как использовать реактивное состояние MobX для управления состоянием приложения, что было довольно интересно. Мы интегрировали его с нашим кодом и Framer-Motion для анимации.

Ресурсы

Репозиторий Github для нашего приложения Todo можно найти здесь, а также он развёрнут на Vercel.

Статья была взята из этого источника:

11
2 комментария

Эх, люблю Mobx, но судя по вакансиям в большинстве проектов все равно зачем-то используется Redux.

2
Ответить

Хорошая статья, есть еще щас Zustand - который еще интереснее. Но по сравнению с Redux, Mobx - рай

Ответить