60. MobX: Observables
MobX: Observables — Наблюдаемые данные 👁️
Заголовок раздела «MobX: Observables — Наблюдаемые данные 👁️»Observable — это сердце MobX. Это специальные данные, за изменениями которых MobX следит автоматически. Как датчики в умном доме: поменялась температура — сразу сработала автоматика! 🏠
1. Три способа сделать данные наблюдаемыми 🛠️
Заголовок раздела «1. Три способа сделать данные наблюдаемыми 🛠️»makeAutoObservable() — Автоматический режим 🤖
Заголовок раздела «makeAutoObservable() — Автоматический режим 🤖»Самый простой и рекомендуемый способ. MobX сам разберётся что сделать observable, что computed, что action:
import { makeAutoObservable } from 'mobx';
class UserStore { name = 'Яша'; // 👈 станет observable age = 25; // 👈 станет observable items: string[] = []; // 👈 станет observable array
constructor() { makeAutoObservable(this); // 🔮 магия здесь }
// Методы автоматически становятся actions setName(name: string) { this.name = name; }
// Геттеры автоматически становятся computed get greeting() { return `Привет, ${this.name}! Тебе ${this.age} лет.`; }}makeObservable() — Ручной режим 🔧
Заголовок раздела «makeObservable() — Ручной режим 🔧»Когда нужен полный контроль — явно указываешь что и как:
import { makeObservable, observable, computed, action } from 'mobx';
class CartStore { items: { name: string; price: number }[] = []; discount = 0;
constructor() { makeObservable(this, { items: observable, discount: observable, total: computed, // явно указываем что это computed addItem: action, // явно указываем что это action setDiscount: action, }); }
get total() { const sum = this.items.reduce((acc, item) => acc + item.price, 0); return sum * (1 - this.discount); }
addItem(item: { name: string; price: number }) { this.items.push(item); }
setDiscount(discount: number) { this.discount = discount; }}observable() — Для объектов и массивов напрямую 📦
Заголовок раздела «observable() — Для объектов и массивов напрямую 📦»import { observable } from 'mobx';
// Наблюдаемый объектconst user = observable({ name: 'Петя', age: 30,});
// Наблюдаемый массивconst todos = observable([ { id: 1, text: 'Купить молоко', done: false }, { id: 2, text: 'Погулять с собакой', done: true },]);
// Наблюдаемое числоimport { observable as obs } from 'mobx';// Для примитивов используем observable.boxconst count = observable.box(0);console.log(count.get()); // 0count.set(5);console.log(count.get()); // 52. Декоратор @observable 🎀
Заголовок раздела «2. Декоратор @observable 🎀»Если используешь декораторы (TypeScript + experimentalDecorators: true):
import { observable, computed, action } from 'mobx';
class TodoStore { @observable todos: Todo[] = []; @observable filter: 'all' | 'active' | 'done' = 'all';
@computed get filteredTodos() { if (this.filter === 'active') return this.todos.filter(t => !t.done); if (this.filter === 'done') return this.todos.filter(t => t.done); return this.todos; }
@action addTodo(text: string) { this.todos.push({ id: Date.now(), text, done: false }); }
@action toggleTodo(id: number) { const todo = this.todos.find(t => t.id === id); if (todo) todo.done = !todo.done; }}3. Observable Arrays — Умные массивы 📋
Заголовок раздела «3. Observable Arrays — Умные массивы 📋»MobX создаёт специальные массивы, которые отслеживают не только замену, но и мутации:
import { observable, autorun } from 'mobx';
const list = observable(['🍎', '🍊', '🍋']);
autorun(() => { console.log('Список:', list.join(', '));});
list.push('🍇'); // ✅ реакция сработаетlist.splice(0, 1); // ✅ реакция сработаетlist[0] = '🍓'; // ✅ реакция сработает (в MobX 6!)list.sort(); // ✅ реакция сработает4. Observable Objects — Умные объекты 🗂️
Заголовок раздела «4. Observable Objects — Умные объекты 🗂️»import { observable, autorun } from 'mobx';
const profile = observable({ name: 'Яша', contacts: { phone: '+7 999 123 45 67', }, tags: ['javascript', 'react'],});
autorun(() => { // MobX отслеживает ВСЕ вложенные поля! console.log(`${profile.name} — ${profile.contacts.email}`);});
profile.name = 'Яков'; // ✅ реакция сработаетprofile.contacts.email = 'new@mail'; // ✅ вложенность тоже работает!profile.tags.push('mobx'); // ✅ и массивы внутри тоже5. Observable Maps — Динамические ключи 🗺️
Заголовок раздела «5. Observable Maps — Динамические ключи 🗺️»Когда ключи объекта заранее неизвестны — используй observable.map():
import { observable, autorun } from 'mobx';
// Обычный объект: MobX не видит ДОБАВЛЕНИЕ новых ключейconst plain = observable({ a: 1 });// Добавление нового ключа НЕ отслеживается в старом подходе!
// Observable Map: отслеживает добавление/удаление ключейconst map = observable.map<string, number>({ 'Яша': 100, 'Петя': 85,});
autorun(() => { console.log('Scores:', map.size, [...map.entries()]);});
map.set('Вася', 92); // ✅ добавление нового ключаmap.delete('Петя'); // ✅ удаление ключаmap.set('Яша', 105); // ✅ изменение значения
// Map удобен для кэшей, хранилищ по IDconst usersById = observable.map<number, User>();usersById.set(1, { id: 1, name: 'Иван' });6. Глубокая vs Поверхностная наблюдаемость 🔍
Заголовок раздела «6. Глубокая vs Поверхностная наблюдаемость 🔍»По умолчанию MobX делает объекты глубоко наблюдаемыми (deep observable). Но иногда нужно отслеживать только изменение ссылки:
import { observable, makeObservable } from 'mobx';
class Store { // Глубокая наблюдаемость (по умолчанию) // MobX наблюдает за всеми вложенными объектами deepData = { nested: { value: 1 } };
// Поверхностная — наблюдает только за самой переменной // Изменение вложенных объектов не отслеживается shallowRef = observable.ref({ nested: { value: 1 } });
// Наблюдает за элементами первого уровня (не за вложенностью) shallowArray = observable.shallow([{ value: 1 }]);
constructor() { makeObservable(this, { deepData: observable, // глубокая shallowRef: observable.ref, // только ссылка shallowArray: observable.shallow, // первый уровень }); }}7. Когда что использовать? 🤔
Заголовок раздела «7. Когда что использовать? 🤔»| Сценарий | Рекомендация |
|---|---|
| Классы (ООП-стиль) | makeAutoObservable(this) |
| Нужен ручной контроль | makeObservable(this, {...}) |
| Простой объект/массив | observable({...}) |
| Динамические ключи | observable.map() |
| Только ссылка | observable.ref |
| Не нужна вложенность | observable.shallow |