от Эджиро Танкгод

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

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

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

Что такое МобХ?

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

Mobx различает следующие понятия в приложении.

  • Состояние
  • Действия
  • Производные

Состояние
Состояние — это данные, которые управляют вашими приложениями. Он содержит различные типы данных, начиная от массивов, строк, чисел и объектов, с которыми MobX позволяет вам работать. Все, что вам нужно сделать, это убедиться, что все свойства, которые вы хотите изменить с течением времени, observable, чтобы 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);

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

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

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

Производные
Все, что получается из состояния, известно как производные, и оно существует в разных формах, но мы рассмотрим различные виды производных MobX:

  • Вычисленные значения
  • Реакции

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

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

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

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 позволяет вам управлять состоянием вашего приложения вне какой-либо структуры. Это делает код несвязанным, переносимым и легко тестируемым, поэтому он называется БЕЗОПАСНЫМ.

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

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

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

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

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

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

Повтор сеанса с открытым исходным кодом

OpenReplay — это альтернатива FullStory и LogRocket с открытым исходным кодом. Это дает вам полную наблюдаемость, воспроизводя все, что ваши пользователи делают в вашем приложении, и показывая, как ваш стек ведет себя для каждой проблемы. OpenReplay размещается на собственном сервере для полного контроля над вашими данными.

Удачной отладки для современных клиентских команд — начните бесплатно отслеживать свое веб-приложение.

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

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

Во-первых, мы настроим нашу среду, создав наше реагирующее приложение с помощью следующей команды на вашем терминале.

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.ts в нашей корневой папке и используем Mobx с React Context API, чтобы сделать наш магазин доступным для всех компонентов.

//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 для создания задач.

//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

Основная укладка выглядит следующим образом.

@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.

Краткое содержание

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

Ресурсы

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

Первоначально опубликовано на blog.openreplay.com 12 мая 2022 г.