W zeszłym tygodniu przedstawiłem Ci podstawy Redux czyli jednej z najpopularniejszych implementacji architektury Flux. W tym tygodniu natomiast, podrzucę Ci trochę informacji na temat odmiennego podejścia do zarządzania stanem aplikacji ReactJS. W związku z tym dziś, jak możesz przeczytać w tytule, prezentuję wprowadzenie do MobX! Czytając dalej dowiesz się co nieco na temat ogólnej koncepcji tego podejścia. Pokażę Ci też prosty przykład użycia biblioteki MobX w połączeniu z komponentami ReactJS.

Na początek jednak sprawdźmy czym właściwie jest biblioteka MobX…

Wprowadzenie do MobX - podstawy

Jak już wspomniałem na wstępie, MobX to biblioteka, dzięki której zarządzanie stanem aplikacji jest niezwykle proste i w bardzo wysokim stopniu skalowalne. Głównym założeniem przyświecającym autorowi tej biblioteki jest oparcie się na “transparentnym reaktywnym programowaniu funkcjonalnym” (ang. transparent functional reactive programming).

Generalnie MobX jest stworzony z myślą o jego wykorzystaniu wraz z ReactJS. Dostarcza on mechanizm, który pozwala na przetrzymywanie oraz automatyczne aktualizacje stanu aplikacji. ReactJS może wykorzystać ten stan podczas renderowania komponentów. Te dwie biblioteki świetnie ze sobą współpracują. Jest to doskonale opisane w dokumentacji:

React provides mechanisms to optimally render UI by using a virtual DOM that reduces the number of costly DOM mutations. MobX provides mechanisms to optimally synchronize application state with your React components by using a reactive virtual dependency state graph that is only updated when strictly needed and is never stale.

Czyli w skrócie: ReactJS optymalnie renderuje interfejs użytkownika; MobX optymalnie synchronizuje stan aplikacji z komponentami ReactJS. Przyznasz, że brzmi nieźle, prawda?

Główne założenia MobX

Na początek spójrz na ogólny schemat działania MobX:

schemat działania MobX

Jak możesz zauważyć na powyższym schemacie, zasada działania MobX oparta jest na czterech konceptach: akcjach, obserwowalnym stanie, wartościach wyliczanych oraz reakcjach. Poniżej przedstawiam omówienie poszczególnych elementów.

Akcje

Akcje w MobX to nic innego jak kod, który zmienia stan aplikacji. W aplikacji ReactJS odbywa się to, na przykład, w metodach obsługi zdarzeń użytkownika (przykład znajdziesz w dalszej części artykułu). W odróżnieniu od Redux, nie ma tutaj większej filozofii: jak najbardziej dozwolone jest zmienianie stanu bezpośrednio. Nie ma potrzeby wywoływania specjalnych funkcji itp. W zasadzie ogólnie MobX wymaga pisania znacznie mniejszej ilości powtarzalnego kodu…

Obserwowalny stan i wartości wyliczane

Kolejna sprawa to obserwowalny stan oraz wartości wyliczane. Myślę, że najlepiej będzie posłużyć się w ich przypadku przykładem:

import { observable, computed } from 'mobx';

class Store {
  @observable name = 'Bartek';
  @computed get decorated() {
    return `${this.name} is awesome!`;
  }
}

export default Store;

W powyższym przykładowym kodzie “store” w MobX widzimy definicję klasy o nazwie (jakże by inaczej) Store. Zawiera ona dwie właściwości: value oraz decorated.

Pierwsza z nich jest właśnie częścią “obserwowalnego stanu”. Aplikacja może zawierać wiele klas podobnych do powyższej i zawierać wiele obserwowalnych właściwości. Wszystkie one składają się właśnie na obserwowalny stan aplikacji. Jak widzisz, do jej zdefiniowania użyłem dekoratora @observable.

Z kolei druga z właściwości powyższej klasy to “wartość wyliczana”, która zależy od wartości name obserwowalnego stanu. Jak się pewnie domyślasz, za każdym razem gdy wartość name się zmieni, wartość zwracana przez funkcję decorated jest przeliczana na nowo. Podobnie jak w przypadku obserwowalnego stanu, wartość wyliczana również definiowana jest za pomocą odpowiedniego dekoratora (@computed).

Na pewno zwróciłeś też uwagę na słowo get stojące przed nazwą funkcji. Oznacza to, że dana wartość wyliczana jest jednocześnie “getterem”. Istnieje również możliwość zdefiniowania “settera” za pomocą słowa set. Należy tylko pamiętać, że “setter” taki nie służy do ustawiania wartości właściwości obserwowalnej tylko do “odwracania” wartości zwracanej przez “getter”. Tutaj możesz przeczytać na ten temat.

Myślę, że warto zwrócić uwagę na jeszcze jedną rzecz. MobX potrafi obserwować nie tylko typy prymitywne ale także referencje, tablice itp., itd. Dlatego też, wartości wyliczane mogę opierać się na przykład, na właściwości obiektu, który przetrzymywany jest w tablicy. Stanowi to moim zdaniem znaczne ułatwienie i może się okazać całkiem przydatne!

Reakcje

Ostatnim zagadnieniem, na którym opiera się MobX są reakcje. Jak dla mnie jest to pojęcie trochę abstrakcyjne…

Generalnie reakcje produkują “efekt uboczny” w momencie zmiany właściwości obserwowalnej. Efekt ten może być na przykład, użyty przez ReactJS do zaktualizowania drzewa komponentów. Spójrz na przykład poniżej, może przemówi on lepiej ode mnie:

autorun(() => {
 console.log("The most awesome is: " + store.name)
});

Funkcja autorun dostarczana przez bibliotekę MobX pozwalana obsługę reakcji. Innymi podobnymi funkcjami są autorunAsync oraz when. W powyższym przykładzie reagujemy na zmianę wartości właściwości store.name wyświetlając ją na ekranie, w konsoli przeglądarki. Czyli de facto można powiedzieć, że w tym przypadku reakcją na zmianę imienia przechowywanego w “store” jest po prostu operacja wyświetlenia tekstu na ekranie…

Reakcje MobX idealnie pasują do aplikacji ReactJS dlatego też wprowadzenie do MobX nie może się ograniczyć tylko do tego co napisałem powyżej. Idźmy więc dalej i spójrzmy jak można użyć MobX razem z Reactem.

Wprowadzenie do MobX - współpraca z ReactJS

Przekształcenie komponentu ReactJS w “reaktywny komponent” wykorzystujący MobX jest bardzo proste. Trzeba jedynie udekorować go dekoratorem @observer:

import React from 'react';
import ReactDOM from 'react-dom';
import { observer } from 'mobx-react';

import Store from './stores/store';

@observer
class App extends React.Component {
  onValueChange(event) {
    // action
    this.props.store.name = event.currentTarget.value;
  }

  render() {
    return (
      <div className="index">
        <span>{this.props.store.decorated}</span>
        <input defaultValue={this.props.store.name}
               onChange={this.onValueChange.bind(this)}
        />
      </div>
   );
  }
}

const store = new Store();
ReactDOM.render(
  <App store={store} />,
  document.getElementById('app')
);

Zgodnie z tym co napisałem, klasa App poprzedzona jest (udekorowana) dekoratorem @observer. Powoduje to “owinięcie” metody render komponentu przez wspomnianą wcześniej funkcję autorun. Dzięki temu MobX jest w stanie utrzymywać synchronizację pomiędzy komponentem i stanem aplikacji. Innymi słowy działa to tak, że za każdym razem, kiedy obserwowalna wartość stanu ulega zmianie, ReactJS jest o tym powiadamiany i może na to zareagować wywołując ponownie metodę render.

Kolejna sprawa do omówienia to akcja. Jak widzisz, element input zawiera obsługę zdarzenia change - odbywa się ona w metodzie onValueChanged omawianej klasy. Tak jak już wspomniałem akcja w MobX to po prostu przypisanie nowej wartości do właściwości obserwowalnej stanu aplikacji. W przykładzie odbywa się to więc po prostu w metodzie obsługi zdarzenia. Jeśli chcesz, możesz oczywiście we własnym zakresie wprowadzić tutaj dodatkową warstwę abstrakcji - MobX jest pod tym względem elastyczny i niczego nie narzuca.

Na końcu przykładu możesz zauważyć jak tworzę i przekazuję “store” do komponentu. Jest to najprostsze podejście użyte na potrzeby prostego przykadu… W kolejnym wpisie przedstawię jak połączyć MobX z react-router i przy tej okazji poruszę trochę głębiej również ten temat.

Wprowadzenie do MobX - podsumowanie

Dzisiejszy wpis to tylko krótkie wprowadzenie do MobX. Mam nadzieję, że udało mi się pokazać jak proste może być zarządzanie stanem aplikacji z użyciem tej biblioteki. Osobiście bardzo podoba mi się, że w porównaniu do Redux nie ma tutaj dodatkowych warstw. Wystarczy jakkolwiek zmienić wartość stanu aplikacji by zmiana ta znalazła odzwierciedlenie w widoku renderowanym przez komponent ReactJS.

Tak jak wspomniałem, to nie koniec wpisów na temat biblioteki MobX w tym tygodniu! Kolejny wpis przedstawi jak powiązać stan MobX z routingiem ReactJS. Zapraszam!