O MobX wspomniałem w jednym z poprzednich wpisów:
Można jednak użyć biblioteki MobX w nowocześniejszy sposób. MobX działa bardzo dobrze z hookami. Współpracuje również z aplikacjami CRA bez konieczności modyfikacji konfiguracji.
Spis treści
Utworzenie aplikacji MobX i dodanie zależności
Utwórzmy więc nową aplikację CRA:
npx create-react-app mobx-2020
A następnie dodajmy do niej 2 zależności jakie będą nam potrzebne, bibliotekę MobX i jej paczkę Reactową:
npm install mobx mobx-react
Użycie MobX
Utworzenie store z zawartością i wyświetlenie
Zacznijmy od utworzenia store
korzystając z hooka useLocalStore
dostarczanego przez mobx-react
:
const store = useLocalStore(() => { return { theme: 'No set' }; });
Hook jako parametr przyjmuje funkcję, która zwróci wartości początkowe.
Następnie stwórzmy Context, do którego przekażemy store by móc odczytywać go w dowolnym miejscu aplikacji (Context również opisałem w poprzednim artykule):
const Context = createContext(); (...) return ( <Context.Provider value={store}> <ThemeDisplay /> </Context.Provider> );
Stwórzmy komponent ThemeDisplay
w którym wyświetlona zostanie wartość ze store
:
const ThemeDisplay = () => { const store = useContext(Context); return <h1>Theme is: {store.theme}</h1>; };
Dodatkowo, możemy od razu opakować zwrotkę w hook useObserver
, który przypilnuje reakcji na zmianę wartości store
:
const ThemeDisplay = () => {
const store = useContext(Context);
return useObserver(() => {
return <h1>Theme is: {store.theme}</h1>;
});
};
Stwórzmy też komponent, w którym będziemy modyfikować zawartość store
, by hook useObserver miał na co reagować
. Również wstrzykniemy store
z kontekstu i na kliknięcie przycisku dokonamy zmiany wartości:
const ThemeSetters = () => {
const store = useContext(Context);
return (
<>
<button onClick={() => store.theme = 'Light'}>Light</button>
<button onClick={() => store.theme = 'Dark'}>Dark</button>
</>
)
};
Computed properties
Computed property to dynamiczna wartość, zwracana na podstawie znajdujących się w stanie innych wartości. Może być to zarówno zwykła funkcja, jak i getter – czyli property oznaczone przedrostkiem get
. Zmodyfikujmy nasz store by zawierał taką wartość:
const store = useLocalStore(() => { return { theme: 'No set', get isSet() { return `${store.theme !== 'No set'}, ${store.theme}`; }, isThemeSet: () => `${store.theme !== 'No set'}, ${store.theme}`, }; });
Dodaliśmy obie wersje – by pokazać, że obie computed properties zostaną zaktualizowane na zmianę stanu. Dodajmy nowy komponent który wyświetli te wartości:
const ThemeSet = () => {
const store = useContext(Context);
return useObserver(() => {
return (
<>
<h2>Is theme set? From getter: {store.isSet}</h2>
<h2>Is theme set? From function: {store.isThemeSet()}</h2>
</>
);
});
};
Wszystkie elementy razem
const App = () => { const store = useLocalStore(() => { return { theme: 'No set', get isSet() { return `${store.theme !== 'No set'}, ${store.theme}`; }, isThemeSet: () => `${store.theme !== 'No set'}, ${store.theme}`, }; }); return ( <Context.Provider value={store}> <ThemeDisplay /> <ThemeSet /> <ThemeSetters /> </Context.Provider> ); };
Klikając przyciski widzimy, że dokonuje się zmiana zarówno w komponencie wyświetlającym dane bezpośrednio, jak i w computed properties.
MobX – pełny przykład:
const Context = createContext();
const ThemeDisplay = () => {
const store = useContext(Context);
return useObserver(() => {
return <h1>Theme is: {store.theme}</h1>;
});
};
const ThemeSetters = () => {
const store = useContext(Context);
return (
<>
<button onClick={() => store.theme = 'Light'}>Light</button>
<button onClick={() => store.theme = 'Dark'}>Dark</button>
</>
)
};
const ThemeSet = () => {
const store = useContext(Context);
return useObserver(() => {
return (
<>
<h2>Is theme set? From getter: {store.isSet}</h2>
<h2>Is theme set? From function: {store.isThemeSet()}</h2>
</>
);
});
};
const App = () => {
const store = useLocalStore(() => {
return {
theme: 'No set',
get isSet() {
return `${store.theme !== 'No set'}`;
},
isThemeSet: () => `${store.theme !== 'No set'}`,
};
});
return (
<Context.Provider value={store}>
<ThemeDisplay />
<ThemeSet />
<ThemeSetters />
</Context.Provider>
);
};
Mobx-react-lite
Jeżeli w aplikacji używamy Reacta w wersji obsługującej hooki (co najmniej 16.8.0) i to z nich głównie korzystamy możemy użyć biblioteki mobx-react-lite – jest ona mniejsza i prostsza niż mobx-react. Co prawda przyszłość samego projekty mobx-react-lite nie jest pewna – ale twórcy zapewniają, że powrót do mobx-react będzie banalnie prosty.
Zaletą zamiany biblioteki będzie zmniejszony rozmiar aplikacji – mobx-react to 14,51 kb (5.09 kb po skompresowaniu GZIP) a mobx-react-lite to 5,36 kb (2,06 kb GZIP)
Podsumowanie
MobX jest mniej popularny od Reduxa, jednak cały czas jest udoskonalany by z każdą wersją być bardziej przyjazny dla programistów. Im więcej dobrych alternatyw, tym lepiej – MobX na pewno jest jedną z nich.