Kas yra MobX ir kodėl jis vis dar aktualus
Kai pradedi kurti React aplikaciją, anksčiau ar vėliau susiduri su būsenos valdymo problema. Komponentų medis auga, duomenys keliauja per props iš vieno komponento į kitą, o tu jau nežinai, kur kas yra ir kodėl viskas neveikia. Redux ilgą laiką buvo de facto standartas, bet prisipažinkime – boilerplate kodo ten tiek, kad galva skauda. Čia ir ateina į pagalbą MobX su savo elegantišku požiūriu į reaktyvumą.
MobX egzistuoja jau nuo 2015 metų, ir nors React ekosistemoje atsirado naujų sprendimų (Zustand, Recoil, Jotai), jis vis dar išlieka vienu iš populiariausių būsenos valdymo sprendimų. Kodėl? Nes jis leidžia rašyti mažiau kodo ir mąstyti intuityviau. Vietoj to, kad rašytum reducers, actions ir selectors, tiesiog padarai objektą observable ir MobX automatiškai seka visus pasikeitimus.
Pagrindinis MobX privalumas – tai jo paprastumas. Jei kada nors dirbai su Excel formule, kur viena langelio reikšmė automatiškai perskaičiuojama, kai keičiasi kita – tai beveik tas pats principas. MobX naudoja observable pattern, kuris yra vienas iš klasikinių programavimo šablonų, tik pritaikytas moderniam JavaScript pasauliui.
Observable pattern esmė be akademinio žargono
Observable pattern – tai būdas organizuoti kodą taip, kad objektai galėtų pranešti kitiems objektams apie savo būsenos pasikeitimus. Įsivaizduok YouTube kanalą: tu užsiprenumeruoji (subscribe), ir kai išeina naujas video, gauni pranešimą. Nereikia kas minutę tikrinti, ar atsirado kažkas naujo – sistema pati tau praneša.
MobX kontekste tai veikia taip: tu paženklini tam tikrus duomenis kaip observable, o komponentus ar funkcijas, kurie naudoja tuos duomenis, kaip observer. Kai observable duomenys pasikeičia, visi observers automatiškai atsinaujina. Nereikia rankiniu būdu kviesti setState ar dispatch – viskas vyksta automatiškai.
Štai paprastas pavyzdys, kaip tai atrodo praktikoje:
„`javascript
import { makeObservable, observable, action } from „mobx”;
class TodoStore {
todos = [];
constructor() {
makeObservable(this, {
todos: observable,
addTodo: action
});
}
addTodo(text) {
this.todos.push({ text, completed: false });
}
}
„`
Matai? Jokių sudėtingų konstrukcijų. Tiesiog klasė su paprastu masyvu ir metodu, kuris prideda elementą. MobX pasirūpina visu reaktyvumu už kulisų.
Kaip MobX seka pasikeitimus po gaubtu
Čia prasideda įdomioji dalis. Kaip MobX žino, kada kažkas pasikeitė? Atsakymas – JavaScript Proxy API. Kai padarai objektą observable, MobX iš tikrųjų sukuria proxy, kuris perima visus bandymus skaityti ar keisti objekto savybes.
Kai komponentas (observer) pirmą kartą renderinasi ir nuskaito kokią nors observable reikšmę, MobX užregistruoja priklausomybę. Tai kaip sekimas – MobX žino, kad šis komponentas domisi šia konkrečia reikšme. Vėliau, kai ta reikšmė pasikeičia, MobX žino tiksliai, kuriuos komponentus reikia atnaujinti.
Tai labai efektyvu, nes skirtingai nuo Redux, kur dažnai atsinaujina visa aplikacija (nebent naudoji sudėtingus selectors su memoization), MobX atnaujina tik tuos komponentus, kurie tikrai naudoja pasikeitusią būseną. Jokio nereikalingo re-renderinimo.
Svarbu suprasti, kad MobX seka tik tuos duomenis, kurie buvo nuskaityti per paskutinį renderį. Jei komponentas nebevartoja kokios nors observable reikšmės, MobX automatiškai nutraukia tą priklausomybę. Tai vadinama „automatic dependency tracking” ir tai viena iš priežasčių, kodėl MobX toks greitas.
Decorators vs makeObservable: kaip rašyti šiuolaikiškai
Jei skaitei senus MobX tutorialus, tikriausiai matei daug @observable ir @action dekoratorių. Jie atrodė elegantiškai, bet problema ta, kad JavaScript decorators vis dar nėra oficialiai standartizuoti (nors jau Stage 3). Dėl to MobX komanda nuo 6 versijos rekomenduoja naudoti makeObservable arba makeAutoObservable.
makeAutoObservable yra tikras laiko taupytojas. Jis automatiškai paverčia visas klasės savybes observable, o metodus – actions:
„`javascript
import { makeAutoObservable } from „mobx”;
class UserStore {
currentUser = null;
isLoading = false;
constructor() {
makeAutoObservable(this);
}
async fetchUser(id) {
this.isLoading = true;
try {
const response = await fetch(`/api/users/${id}`);
this.currentUser = await response.json();
} finally {
this.isLoading = false;
}
}
logout() {
this.currentUser = null;
}
}
„`
Paprasta, tiesa? Nereikia atskirai specifikuoti, kas yra observable, kas action. MobX pats išsiaiškina pagal tai, ar tai funkcija, ar paprastas duomuo.
Tačiau kartais nori daugiau kontrolės. Pavyzdžiui, gali turėti privačius metodus, kurie neturėtų būti actions, arba computed reikšmes. Tada naudoji makeObservable su explicit konfigūracija:
„`javascript
import { makeObservable, observable, computed, action } from „mobx”;
class ShoppingCart {
items = [];
taxRate = 0.21;
constructor() {
makeObservable(this, {
items: observable,
taxRate: observable,
total: computed,
addItem: action,
removeItem: action
});
}
get total() {
const subtotal = this.items.reduce((sum, item) => sum + item.price, 0);
return subtotal * (1 + this.taxRate);
}
addItem(item) {
this.items.push(item);
}
removeItem(itemId) {
this.items = this.items.filter(item => item.id !== itemId);
}
}
„`
Computed values: efektyvumas, kurį įvertinsi vėliau
Computed values yra viena iš galingiausių MobX savybių, kurią pradedantieji dažnai neįvertina. Tai funkcijos, kurių rezultatas priklauso nuo observable reikšmių, bet pats rezultatas yra cacheinamas ir perskaičiuojamas tik tada, kai pasikeičia priklausomos reikšmės.
Įsivaizduok, kad turi prekių krepšelį su šimtais produktų ir nori apskaičiuoti bendrą sumą. Be computed, kiekvieną kartą renderinant komponentą tektų iš naujo skaičiuoti visą sumą. Su computed, MobX apskaičiuoja vieną kartą ir naudoja tą pačią reikšmę, kol nepasikeičia items masyvas.
„`javascript
class ProductList {
products = [];
searchQuery = „”;
selectedCategory = „all”;
constructor() {
makeAutoObservable(this);
}
get filteredProducts() {
return this.products.filter(product => {
const matchesSearch = product.name
.toLowerCase()
.includes(this.searchQuery.toLowerCase());
const matchesCategory =
this.selectedCategory === „all” ||
product.category === this.selectedCategory;
return matchesSearch && matchesCategory;
});
}
get productCount() {
return this.filteredProducts.length;
}
}
„`
Čia filteredProducts perskaičiuojamas tik tada, kai pasikeičia products, searchQuery arba selectedCategory. Jei renderini komponentą, kuris naudoja productCount, bet niekas iš tų trijų reikšmių nepasikeitė, MobX grąžins cachintą rezultatą. Nereikės net vykdyti filter funkcijos.
Svarbus niuansas: computed values turėtų būti „pure” funkcijos – t.y. neturėtų keisti būsenos ar turėti šalutinių efektų. Jie skirta tik duomenų transformacijai ir išvedimui.
Actions ir reakcijos: kada ir kaip keisti būseną
MobX filosofija paprasta: visus būsenos pakeitimus turėtų vykdyti actions. Tai ne tik gera praktika, bet ir leidžia MobX optimizuoti atnaujinimus. Kai keiti kelis observable laukus viename action, MobX sugrupuoja visus pakeitimus ir atnaujina UI tik vieną kartą, o ne po kiekvieną atskirą pakeitimą.
Jei naudoji strict mode (o turėtum), MobX net neleistų keisti observable reikšmių už action ribų:
„`javascript
import { configure } from „mobx”;
configure({
enforceActions: „always”
});
„`
Tai puiki apsauga nuo atsitiktinių būsenos pakeitimų, kurie gali atsitikti bet kur kode. Su šia konfigūracija, jei bandysi tiesiogiai priskirti store.value = 123 ne action viduje, gausi klaidą.
Bet kartais nori, kad kažkas įvyktų automatiškai, kai pasikeičia tam tikra būsena. Tam yra reactions. MobX turi tris pagrindinius reakcijų tipus:
autorun – vykdomas iš karto ir kiekvieną kartą, kai pasikeičia bet kuri jame naudojama observable reikšmė:
„`javascript
import { autorun } from „mobx”;
autorun(() => {
console.log(`User: ${userStore.currentUser?.name || „Guest”}`);
});
„`
reaction – panašus į autorun, bet leidžia atskirti, kokius duomenis sekti ir ką daryti, kai jie pasikeičia:
„`javascript
import { reaction } from „mobx”;
reaction(
() => userStore.currentUser,
(user) => {
if (user) {
analyticsService.trackUser(user.id);
}
}
);
„`
when – vykdomas vieną kartą, kai sąlyga tampa true:
„`javascript
import { when } from „mobx”;
when(
() => userStore.isAuthenticated,
() => {
router.navigate(„/dashboard”);
}
);
„`
Integracija su React: observer HOC ir hooks
MobX su React dirba per mobx-react-lite biblioteką (arba mobx-react, jei naudoji class komponentus). Pagrindinis įrankis – observer funkcija, kuri paverčia tavo komponentą reaktyviu.
„`javascript
import { observer } from „mobx-react-lite”;
const TodoList = observer(({ store }) => {
return (
Užduotys ({store.todos.length})
{store.todos.map(todo => (
/>
{todo.text}
))}
);
});
„`
Kai komponentas yra observer, jis automatiškai re-renderinsis, kai pasikeičia bet kuri observable reikšmė, kurią jis naudoja. Nereikia useState, useEffect ar kitų React hooks būsenos valdymui – MobX pasirūpina viskuo.
Jei nori sukurti store instance komponentui, naudok useLocalObservable:
„`javascript
import { useLocalObservable } from „mobx-react-lite”;
const Counter = observer(() => {
const store = useLocalObservable(() => ({
count: 0,
increment() {
this.count++;
},
decrement() {
this.count–;
}
}));
return (
Count: {store.count}
);
});
„`
Globaliniam store paprastai naudojamas React Context:
„`javascript
import { createContext, useContext } from „react”;
const StoreContext = createContext(null);
export const StoreProvider = ({ children, store }) => {
return (
{children}
);
};
export const useStore = () => {
const store = useContext(StoreContext);
if (!store) {
throw new Error(„useStore must be used within StoreProvider”);
}
return store;
};
// Naudojimas komponente
const MyComponent = observer(() => {
const store = useStore();
return
;
});
„`
Kai viskas susideda į vieną paveikslą
MobX nėra magija, nors kartais taip atrodo. Tai gerai apgalvotas reaktyvumo mechanizmas, kuris leidžia rašyti mažiau kodo ir mąstyti natūraliau. Vietoj to, kad nuolat galvotum apie tai, kaip duomenys keliauja per aplikaciją, tiesiog keiti būseną, o UI atsinaujina automatiškai.
Ar MobX tinka kiekvienai aplikacijai? Ne. Jei kuri labai mažą projektą, galbūt pakaks React useState. Jei reikia strict predictability ir time-travel debugging, Redux vis dar geresnis pasirinkimas. Bet jei nori produktyvumo, mažiau boilerplate ir intuityvaus API – MobX yra puikus variantas.
Pradėti paprasta: sukurk store klasę su makeAutoObservable, apgaubk komponentus su observer, ir viskas veiks. Vėliau, kai projektas augs, galėsi išnaudoti pažangesnes funkcijas – computed values optimizacijai, reactions šalutiniams efektams, strict mode saugumui.
Svarbiausia – nebijok eksperimentuoti. MobX dokumentacija puiki, community aktyvus, o kai kartą suprasi observable pattern logiką, pradėsi jį matyti ir kitose technologijose. Reactive programming nėra tik MobX – tai mąstymo būdas, kuris tampa vis populiaresnis modernių aplikacijų kūrime.
