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

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

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

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

Что такое MobX?

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

@react_tg – react в телеграме.

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

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

State

Состояние (state ориг.) это сердце каждого приложения и нет более быстрого способа создания забагованого, неуправляемого приложения, как отсутствие консистентности состояния. Или состояние, которое несогласованно с локальными переменными вокруг. Поэтому множество решений по управлению состоянием пытаются ограничить способы, которыми можно его изменять, например сделать состояние неизменяемым. Но это порождает новые проблемы, данные нуждаются в нормализации, нет гарантии ссылочной целостности и становится почти невозможно использовать такие мощные концепты как прототипы(prototypes ориг.) Ниже приведён простой пример:

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 – это любой блок кода, который может изменять такие данные: пользовательские события, внутренние данные и т.д.

Actions (Действия) это все те штуки которые меняют состояние. MobX проследит, чтобы все изменения в состоянии приложения, вызванные действиями, автоматически обработались всеми производными и реакциями. Синхронно и без помех.

Action похоже на человека, который изменяет данные в ячейке электронной таблицы. В приведённом выше коде, мы можем видеть методы 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.

Mobx лучше использовать когда вы не хотите использовать монструозные конструкуции чтобы обновить какой-то объект который находится где-то глубоко внутри дерева состояния, например когда вам нужно обновить комментарий который находится внутри массива комментария объекта “задачи”, которая находится внутри массива задач объекта “проекта” (схема примерно такая

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

Mobx лучше использовать когда вы хотите чтобы какие-то объекты ссылались друг на друга. Например хотим создать todo-приложение но чтобы там можно было создавать подзадачи к задаче и подзадачи к подзадачам в общем чтобы получилось некое дерево подзадач. Со стороны реакта будет один компонент который рекурсивно будет вызвать себя для всех подзадач.

const Task = ({task})=> (
  <div>{task.children.map(subtask=><Task task={subtask}>)}</div>
  )

Когда использовать 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;
Управление состоянием в React с помощью MobX 2023
И наше приложение Todo, похоже, работает очень хорошо, обрабатывая своё внутреннее состояние с помощью MobX

Заключение

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

Ресурсы

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

+1
1
+1
2
+1
1
+1
0
+1
0

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *