MobX

Функция makeObservable делает класс отслеживаемым. Функция makeAutoObservable делает класс отслеживаемым по переданному контексту и самостоятельно производит нужные для неё настройки.

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

Сейчас нам нужно создать класс, который будет выполнять определённый функционал на странице (в виде методов). В конце нужно просто экспортировать один инстанс объекта класса.

State / store / counter.ts

import { makeAutoObservable } from 'mobx';
 
class Counter {
   // переменная, которая является состоянием
   count = 0;
 
   constructor() {
      // настраивает работу с mobx
      makeAutoObservable(this);
   }
 
   increment() {
      this.count = this.count + 1;
   }
 
   decrement() {
      this.count = this.count - 1;
   }
}
 
export default new Counter();

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

State / State.tsx

import React from 'react';
import { useState } from 'react';
import styles from './State.module.scss';
import { Button } from '@/components';
import counter from './store/counter';
import { observer } from 'mobx-react-lite';
 
export const State = observer((): JSX.Element => {
   return (
      <div className={styles.wrapper}>
         <h2 className={styles.title}>Счётчик:</h2>
         <h1 className={styles.num}>{counter.count}</h1>
         <Button
            buttonType={'gray'}
            className={styles.reduce}
            onClick={() => counter.decrement()}
         >
            Уменьшить
         </Button>
         <Button
            buttonType={'purple'}
            className={styles.increase}
            onClick={() => counter.increment()}
         >
            Увеличить
         </Button>
      </div>
   );
});

И теперь наш счётчик работает и быстро отзывается на все действия

Далее приведём ещё один пример, где мы сразу добавим данные и реализуем список дел

Todo / store / todo.store.ts

import { makeAutoObservable } from 'mobx';
 
interface ITodo {
   id: number;
   title: string;
   completed: boolean;
}
 
class TodoStore {
   todos: ITodo[] = [
      { id: 1, title: 'Сходить в магазин', completed: false },
      { id: 2, title: 'Купить хлеб', completed: false },
      { id: 3, title: 'Съесть хлеб', completed: false },
   ];
 
   constructor() {
      makeAutoObservable(this);
   }
 
   addTodo(todo: ITodo) {
      this.todos.push(todo);
   }
 
   removeTodo(id: number) {
      this.todos = this.todos.filter(todo => todo.id !== id);
   }
 
   completeTodo(id: number) {
      this.todos = this.todos.map(todo =>
         todo.id === id ? { ...todo, completed: !todo.completed } : todo,
      );
   }
}
 
export default new TodoStore();

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

TodoList / TodoList.tsx

import React from 'react';
import { observer } from 'mobx-react-lite';
import todo from './store/todo.store';
import styles from './TodoList.module.scss';
import { Button, Input } from '@/components';
 
export const TodoList = observer(() => {
   return (
      <div className={styles.wrapper}>
         {todo.todos.map(t => (
            <div key={t.id} className={styles.todo}>
               <Input
                  type={'checkbox'}
                  checked={t.completed}
                  onChange={() => todo.completeTodo(t.id)}
               />
               <div>{t.title}</div>
               <Button
                  className={styles.button}
                  buttonType={'purple'}
                  onClick={() => todo.removeTodo(t.id)}
               >
                  X
               </Button>
            </div>
         ))}
      </div>
   );
});

И теперь наш список дел удаляет себя и может быть выполненным

Так же нужно сказать, что если нам нужно работать с более глубокими уровнями вложенности, что в моб нужно передать опцию deep

Так же мы можем уточнить внутри оверрайда (второй объект моба):

  • что есть отслеживаемый объект todos: observable,
  • что есть экшены addTodo: action
  • И что является вычисляемым значением computed: значение
constructor() {
   makeAutoObservable(this, { todos: observable, addTodo: action, computed:  });
}

Вычисляемые значения - это значения, которые мы считаем в методе, помеченном ключевым словом get. Этот метод обязательно должен вычислять результат каких-то вычислений. Основное преимущество в том, что метод вызывается только тогда, когда один из его параметров изменил своё значение. Такой подход оптимизирует выполнение значений.

get total() {
   return `Counter = ${this.timer + this.count}`;
}

Теперь тут применяем функцию

<h1 className={styles.num}>{counter.total}</h1>

Асинхронные экшены. Они используются ровно так же, как и остальные экшены. Тут представлен пример получения списка дел с сервера jsonplaceholder

todo.store.ts

fetchTodos() {
   fetch('https://jsonplaceholder.typicode.com/todos/')
      .then(response => response.json())
      .then(json => {
         this.todos = [...this.todos, ...json];
      });
}

Тут при нажатии кнопки мы получаем данный массив

TodoList.tsx

<Button buttonType={'gray'} onClick={() => todo.fetchTodos()}>
   Получить список дел
</Button>

И видим огромный список дел