Dowiedz się, w jaki sposób używać MobX w świecie Reacta A.D. 2020

MobX 2020

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

  1. Utworzenie aplikacji MobX i dodanie zależności
  2. Użycie MobX
    1. Utworzenie store z zawartością i wyświetlenie
    2. Computed properties
    3. Wszystkie elementy razem
    4. MobX – pełny przykład
  3. Mobx-react-lite
  4. Podsumowanie

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.