191104 TIL MobX 6.X, MobX vs ContextAPI

» 1.5막, TIL (Today I Learned)

오늘 할 일

  1. React Hooks + MobX + TS 복습

React를 함수형으로 쓰고 싶은데, 그러면 mobx-react-lite 써야한다고 한다.
그래서 막 써보고 있는데, 또 mobx 버젼 6.xx 이상은 그냥 mobx-react를 써도 된다고 한다. 하핳

Using MobX with React Hooks and TypeScript 이 tutorial을 찬찬히 다시 따라해보는 중…



//city.tsx  
import React from 'react';

export const CityView: React.FC<{ cities: string[] }> = ({cities}) => {
    return (
        <ul>
            {cities.map(city => <li>{city}</li>)}
        </ul>
    );
}

export const CityList = () => {
    return <CityView cities={[]} />
}

export default CityList;

// store.ts
const Cities = [
    'London', 'Seoul', 'LA'
];

export const createStore = () => {
    const store = {
        get allCities() {
            return Cities;
        }
    };

    return store;
};

export type TStore = ReturnType<typeof createStore>

// context.tsx 
import React from 'react';
import { useLocalStore } from 'mobx-react';
import { createStore, TStore } from "./store/store";

export const storeContext = React.createContext<TStore | null>(null);

export const StoreProvider: React.FC = ({children}) => {
    const store = useLocalStore(createStore);

    return (
        <storeContext.Provider value={store}>
            {children}
        </storeContext.Provider>
    );
};

export default StoreProvider;

storeContext를 콘솔에 찍어보면 아래처럼 나온다.

$$typeof: Symbol(react.context)
Consumer: {$$typeof: Symbol(react.context), _context: {}, _calculateChangedBits: null, }
Provider: {$$typeof: Symbol(react.provider), _context: {}}
_calculateChangedBits: null
_currentRenderer: {}
_currentRenderer2: null
_currentValue: null
_currentValue2: null
_threadCount: 0
__proto__: Object

리액트 16.3 에 소개된 새로워진 Context API 파헤치기 이 글을 보면 아래처럼 context를 설명한다.

Context 는 createContext 라는 함수를 사용해서 만들며, 이 함수를 호출하면 Provider 와 Consumer 라는 컴포넌트들이 반환됩니다. Provider 는 Context 에서 사용 할 값을 설정할 때 사용되고, Consumer 는 나중에 우리가 설정한 값을 불러와야 할 때 사용됩니다.

정리하면,,,

  1. store에는 initial values와 사용할 함수를 선언하여 export
  2. context에서는 1) Context와 Provider를 만들어서 export 2) Context에서는 사용할 store를 불러오고, 3) Provider에서는 store 불러와서 이 store를 Provider에 넣어준다…

    ? 질문 ?

    • StoreProvider에서 createStore하는데, storeContext 필요한 이유는?
    • mobx useLocalStore 역할?

    후잉 MobX 다시 공부해야돼.


useLocalStore

https://mobx-react.js.org/state-local

useLocalStore<T, S>(initializer: () => T, source?: S): T

Local observable state can be introduced by using the useLocalStore hook, that runs its initializer function once to create an observable store and keeps it around for a lifetime of a component.

로컬 observable state는 useLocalStore hook을 사용해 소개될 수 있다. useLocalStore는 initializer 함수를 한번 실행시키는데, observable store를 생성하고, 컴포넌트의 lifetime 동안 유지한다.

All properties of the returned object will be made observable automatically, getters will be turned into computed properties, and methods will be bound to the store and apply mobx transactions automatically. If new class instances are returned from the initializer, they will be kept as is.

return된 객체의 모든 property는 자동적으로 observable될 수 있다. getter는 computed property로 변할 것이고, method는 store에 bind될 것이며 mobx transaction을 자동적으로 적용할 것이다. 만약 새로운 인스턴스가 initializer에서 반환되면, 그 인스턴스는 그 상태로 유지될 것이다.


import React from 'react'
import { useLocalStore, useObserver } from 'mobx-react' // 6.x

export const SmartTodo = () => {
    const todo = useLocalStore(() => ({
        title: 'Click to toggle',
        done: false,
        toggle() {
            todo.done = !todo.done
        },
        get emoji() {
            return todo.done ? '😜' : '🏃'
        },
    }))

    return useObserver(() => (
        <h3 onClick={todo.toggle}>
            {todo.title} {todo.emoji}
        </h3>
    ))
}

실제 LocalStorage를 쓰지는 않는군…



// city.tsx
import React from 'react';
import { useObserver } from 'mobx-react';
import { storeContext } from "../context";

export const CityView: React.FC<{ cities: string[] }> = ({cities}) => {
    return (
        <ul>
            {cities.map(city => <li>{city}</li>)}
        </ul>
    );
}

export const CityList = () => {
    const store = React.useContext(storeContext);
    if (!store) throw Error('Store is NULL');
    
    return useObserver(() => {
        return <CityView cities={store.allCities}/>
    })
}

export default CityList;

바로 다음 단계에 나옴..

Each component is now place, but we are rendering a hardcoded empty list. Let’s connect the <CityList /> component to our store. We will access the store instance in <StoreProvider /> via the React.useContext hook


예제에는 없지만 city 추가하는 걸 만들어보겠어.


//store.ts
import {observable} from "mobx";

const Cities = observable([
    'London', 'Seoul', 'LA'
]);

export const createStore = () => {
    const store = {
        city: observable.box(''),
        setCity(city: string) {
            store.city.set(city);
        },
        addCity() {
            Cities.push(store.city.get());
        },
    };
    return store;
};

export type TStore = ReturnType<typeof createStore>

//createCity.tsx 
import React from 'react';
import { useObserver } from 'mobx-react'
import { storeContext } from "../context";

const Create: React.FC = () => {
    const store = React.useContext(storeContext);
    if (!store) throw Error('store is null');

    const {city, setCity} = store;
    return useObserver(() => {
        return (
            <div>
                <input value={city.get()} onChange={e => setCity(e.target.value)}/>
                <button onClick={store.addCity}>추가</button>
            </div>
        )
    });
}

export default Create

되긴 되는데, 이게 맞는지 모르겠다 엉엉 😭😭😭😭


Build a Recipe App With React | React Tutorial For Beginners

쉼쉼해서 요런거 보고 따라 했다.

API 주소: https://developer.edamam.com/edamam-docs-recipe-api
useEffect를 잘 써보았다.
짧고 핵심만 딱 있는 좋은 강의였음.