← Назад к вопросам

Как глубоко знаешь MobX?

2.2 Middle🔥 182 комментариев
#JavaScript Core

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Глубокие знания MobX

MobX - это библиотека для управления состоянием приложения на основе реактивного программирования. Я имею глубокие практические знания MobX и использовал его в production-приложениях. Поделюсь ключевыми концепциями и примерами.

Основные концепции MobX

1. Observable (Наблюдаемое состояние)

Observable - это объекты, которые отслеживаются MobX для изменений. MobX автоматически отслеживает, когда observable изменяется.

import { makeObservable, observable, action } from 'mobx';

class UserStore {
  // Явное объявление observable
  users: User[] = [];
  isLoading = false;
  error: string | null = null;

  constructor() {
    makeObservable(this, {
      users: observable,
      isLoading: observable,
      error: observable,
      setLoading: action,
      setUsers: action,
      setError: action,
    });
  }

  setLoading(value: boolean) {
    this.isLoading = value;
  }

  setUsers(users: User[]) {
    this.users = users;
  }

  setError(error: string | null) {
    this.error = error;
  }
}

Alternative способ с декораторами (если используешь TypeScript):

class UserStore {
  @observable users: User[] = [];
  @observable isLoading = false;
  @observable error: string | null = null;

  @action
  setLoading(value: boolean) {
    this.isLoading = value;
  }
}

2. Computed (Вычисляемые значения)

Computed создают значения, которые автоматически пересчитываются при изменении зависимостей. Они кэшируются и пересчитываются только при необходимости.

class UserStore {
  @observable users: User[] = [];
  @observable filter = '';

  // Computed свойство
  @computed
  get filteredUsers(): User[] {
    console.log('Filtering users...');
    return this.users.filter(user => 
      user.name.toLowerCase().includes(this.filter.toLowerCase())
    );
  }

  // Или с makeObservable
  constructor() {
    makeObservable(this, {
      users: observable,
      filter: observable,
      filteredUsers: computed,
      activeUsers: computed,
      setFilter: action,
    });
  }

  get activeUsers(): User[] {
    return this.users.filter(u => u.active);
  }
}

// Использование
const store = new UserStore();
store.filter = 'john';
console.log(store.filteredUsers); // Computed выполнится один раз
console.log(store.filteredUsers); // Возьмется кэшированное значение
store.filter = 'jane';
console.log(store.filteredUsers); // Пересчитается, т.к. filter изменился

3. Actions (Действия для изменения состояния)

Actions - это функции, которые изменяют observable. MobX отслеживает все изменения внутри actions и запускает observers только один раз после завершения action.

class UserStore {
  @observable users: User[] = [];
  @observable isLoading = false;

  @action
  async loadUsers() {
    this.isLoading = true;
    try {
      const response = await fetch('/api/users');
      const data = await response.json();
      this.users = data; // Одно изменение для observer
      this.isLoading = false; // Второе изменение
      // Observer запустится один раз, после завершения action
    } catch (error) {
      this.isLoading = false;
    }
  }

  // Асинхронный action
  @action
  async updateUser(id: string, data: Partial<User>) {
    const user = this.users.find(u => u.id === id);
    if (user) {
      Object.assign(user, data);
    }
  }
}

4. Reactions (Реакции)

Reactions позволяют выполнять side effects при изменении observable.

import { reaction, autorun } from 'mobx';

class UserStore {
  @observable searchQuery = '';
  @observable results: User[] = [];

  constructor() {
    // autorun - выполняется сразу и при изменении зависимостей
    autorun(() => {
      console.log('Search query:', this.searchQuery);
    });

    // reaction - более тонкий контроль
    reaction(
      () => this.searchQuery,
      (query) => {
        // Выполнится только при изменении searchQuery
        this.performSearch(query);
      },
      {
        delay: 500 // Debounce
      }
    );
  }

  @action
  async performSearch(query: string) {
    const data = await fetch(`/api/search?q=${query}`).then(r => r.json());
    this.results = data;
  }
}

Использование с React

5. Observer компонент

import { observer } from 'mobx-react-lite';
import { createContext, useContext } from 'react';

const UserStoreContext = createContext<UserStore | null>(null);

const useUserStore = () => {
  const store = useContext(UserStoreContext);
  if (!store) throw new Error('Store not provided');
  return store;
};

// Observer компонент автоматически подписывается на изменения
const UserList = observer(() => {
  const store = useUserStore();

  return (
    <div>
      <input
        value={store.filter}
        onChange={(e) => store.setFilter(e.target.value)}
        placeholder="Filter users"
      />
      {store.isLoading && <p>Loading...</p>}
      <ul>
        {store.filteredUsers.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
      <p>Found: {store.filteredUsers.length}</p>
    </div>
  );
});

const UserStats = observer(() => {
  const store = useUserStore();
  return <div>Total users: {store.users.length}</div>;
});

Продвинутые техники

6. Логирование изменений

import { spy } from 'mobx';

spy((event) => {
  if (event.type === 'action') {
    console.log(`Action: ${event.name}`);
  }
  if (event.type === 'reaction') {
    console.log('Reaction triggered');
  }
});

7. Flow для асинхронности

import { flow } from 'mobx';

class UserStore {
  @observable user: User | null = null;
  @observable isLoading = false;

  // flow - лучше чем обычный async/await
  loadUser = flow(function* (id: string) {
    this.isLoading = true;
    try {
      const response: User = yield fetch(`/api/users/${id}`).then(r => r.json());
      this.user = response;
    } finally {
      this.isLoading = false;
    }
  });
}

8. Runnable examples

class TodoStore {
  @observable todos: Todo[] = [];
  @observable filter: 'all' | 'completed' | 'pending' = 'all';

  @computed
  get filteredTodos(): Todo[] {
    switch (this.filter) {
      case 'completed':
        return this.todos.filter(t => t.completed);
      case 'pending':
        return this.todos.filter(t => !t.completed);
      default:
        return this.todos;
    }
  }

  @computed
  get stats() {
    return {
      total: this.todos.length,
      completed: this.todos.filter(t => t.completed).length,
      pending: this.todos.filter(t => !t.completed).length,
    };
  }

  @action
  addTodo(text: string) {
    this.todos.push({
      id: Math.random(),
      text,
      completed: false,
    });
  }

  @action
  toggleTodo(id: number) {
    const todo = this.todos.find(t => t.id === id);
    if (todo) {
      todo.completed = !todo.completed;
    }
  }
}

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

Плюсы:

  • Минимальный boilerplate кода
  • Автоматическое отслеживание зависимостей
  • Отличная производительность благодаря кэшированию computed
  • Легко читать и понимать код

Минусы:

  • Может быть сложнее отследить, где изменилось состояние
  • Требует большей дисциплины разработчиков
  • Не так популярен как Redux в больших командах

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