All Articles

React + Mobx 구현하기 (2)

mobx-react

mobx-react는 obeerver을 포함하고 있기 때문에, observable이 바뀔 때마다 컴포넌트를 리렌더링 해준다.


사전 추가 설치

  • @babel/plugin-proposal-decorators (^7.6.0)

    • webpack.config.js 설정 필요 (plugins)

      plugins: [
      "react-hot-loader/babel",
      ["@babel/plugin-proposal-decorators", { legacy: true }],
      ["@babel/plugin-proposal-class-properties", { loose: true }],
      ]
  • mobx-react


데코레이터 (Decorator)

데코레이터는 함수 !!

@observer
class App extends Component {}
observer(App)

위의 두 개가 같음.

@observable 사용하지 못하는 경우가 있기 때문에, 데코레이터 사용하지 않는 것을 권장.

데코레이터는 class인 경우에만 사용 가능 !


1. class 컴포넌트

// client.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { hot } from 'react-hot-loader/root';
import App from './App';

const Hot = hot(App);

ReactDOM.render(
  <Hot />,
  document.querySelector('#root'),
);
// App.jsx
import React, { Component } from 'react';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import { userStore, postStore } from './store';

@observer
class App extends Component {

  // local state
  state = observable({
    name: '',
    password: '',
  });

  onLogIn = () => {
    userState.logIn({
      nickname: 'zerocho',
      password: '비밀번호',
    });
  };

  onLogout = () => {
    userStore.logOut();
  };

  onChangeName = (e) => {
    this.state.name = e.target.value;
  };

  onChangePassword = (e) => {
    this.state.password = e.target.value;
  }
  render() {
    return (
      <div>
        {userStore.isLoggingIn
          ? <div>로그인 중</div>
          : userStore.data
            ? <div>{userStore.data.nickname}</div>
            : '로그인 해주세요.'}
        {!userStore.data
          ? <button onClick={this.onLogIn}>로그인</button>
          : <button onClick={this.onLogout}>로그아웃</button>}
        <div>{postStore.data.length}</div>
        <div>
         <input value={this.state.name} onChange={this.onChangeName}/>
         <input value={this.state.password} type="password" onChange={this.onChangePassword}/>
        </div>
      </div>
    );
  }
}

export default App;
// store.js
const { observable } from 'mobx';

const userStore = observable({
  isLoggingIn: false,
  data: null,
  logIn(data) { // action
    this.isLoggingIn = true;
    setTimeout(() => {
      this.data = data;
      this.isLoggingIn = false;
      postStore.data.push(1); // redux에 비해 편한 점
    }, 2000);
  },
  logOut() {
    this.data = null;
  },
});

const postStore = observable({
  data: [],
  addPost(data) {
    this.data.push(data);
  },
});

export { userStore, postStore };

mobx는 비동기 미들웨어가 필요없는 이유

  • 액션 안에 setTimeout, axios 등 원하는 비동기 사용하면 된다.

mobx dev-tools

  • 크롬 mobx 검색 —> MobX Developer Tools

  • 가독성이 조금 떨어짐


mobx-state-tree (MST)

  • Mobx가 너무 자유로워서 동작은 잘 되지만, 찜찜하다고 생각할 때 사용 (틀 혹은 체계 잡기 위해 사용)

Provider로 감싸는 이유

// client.jsx
ReactDOM.render(
  <Provider>
    <Hot />
  </Provider>,
  document.querySelector('#root'),
);

Provider을 더 이상 사용할 필요 없다. @inject 혹은 inject(App) 사용할 때 사용했었다. (inject를 사용하면 props로 들어가게 되어 this.props.userStore ~ 이렇게 사용 가능)

React에서 Provider랑 inject 더 이상 쓰지 말라고 합니다. 그 이유는 ContextAPI가 나왔기 때문입니다.

결론은 취향 차이 !

이 강의자는 Provider를 사용하는 것을 좋아하지 않음


2. Hooks 컴포넌트

// App.jsx
import React, { useCallback } from 'react';
import { useObserver } from 'mobx-react';
import { userStore, postStore } from './store';

const App = () => {
  const onLogIn = useCallback(() => {
    userStore.logIn({
      nickname: 'zerocho',
      password: '비밀번호',
    });
  }, []);

  const onLogout = useCallback(() => {
    userStore.logOut();
  }, []);

  return useObserver(() => (
    <div>
      {userStore.isLoggingIn
        ? <div>로그인 중</div>
        : userStore.data
          ? <div>{userStore.data.nickname}</div>
          : '로그인 해주세요.'}
      {!userStore.data
        ? <button onClick={onLogIn}>로그인</button>
        : <button onClick={onLogout}>로그아웃</button>}
      <div>{postStore.data.length}</div>
      <div>
       <input value={state.name} onChange={state.onChangeName}/>
       <input value={state.password} type="password" onChange={state.onChangePassword}/>
      </div>
    </div>
  ));
};

class App extends Component {

  // local state
  state = observable({
    name: '',
    password: '',
  });

  onLogIn = () => {
    userState.logIn({
      nickname: 'zerocho',
      password: '비밀번호',
    });
  };

  onLogout = () => {
    userStore.logOut();
  };

  onChangeName = (e) => {
    this.state.name = e.target.value;
  };

  onChangePassword = (e) => {
    this.state.password = e.target.value;
  }
  render() {
    return (
      <div>
        {userStore.isLoggingIn
          ? <div>로그인 중</div>
          : userStore.data
            ? <div>{userStore.data.nickname}</div>
            : '로그인 해주세요.'}
        {!userStore.data
          ? <button onClick={this.onLogIn}>로그인</button>
          : <button onClick={this.onLogout}>로그아웃</button>}
        <div>{postStore.data.length}</div>
        <div>
         <input value={this.state.name} onChange={this.onChangeName}/>
         <input value={this.state.password} type="password" onChange={this.onChangePassword}/>
        </div>
      </div>
    );
  }
}

export default App;

하나의 컴포넌트 안에서 동작하는 state

import { useLocalStore } from 'mobx-react';

const App = () => {
  const state = useLocalStore(() => ({
    name: '',
    password: '',
    onChangeName(e) {
      this.name = e.target.value;
    },
    onChangePassword(e) {
      this.password = e.target.value;
    }
  })));

...
}

엄밀히 말하면 mobx가 워낙 자유롭기 때문에 다른 컴포넌트에서 사용할 수 있긴 있음.


여러 Store를 묶어주는 역할

// Context.jsx
import React from 'react';
import { userStore, postStore } from './store';

export const storeContext = React.createContext({
  userStore,
  postStore,
});

export const StoreProvider = ({ children }) => {
  return (
    <storeContext.Provider>
     {children}
    </storeContext.Provider>
  );
};

export default StoreProvider;
// client.jsx
ReactDOM.render(
  <StoreProvider>
    <Hot />
  </StoreProvider>,
  document.querySelector('#root')
);
// useStore.js - custom hooks
import React from 'react';
import { storeContext } from './Context';

function useStore() {
  const { userStore, postStore } = React.useContext(storeContext);

  return { userStore, postStore };
}
// App.jsx
import useStore from './useStore';

const App = () => {
  const { useStore, postStore } = useStore();
  ...
}

위처럼 ContextAPI 방법으로 store를 불러오는 방법이 있다.

1) import하여 global적으로 사용할 것인지,

  1. ContextAPI를 통하여 묶어 주어 사용할 것인지

선택하여 사용할 수 있다.


결론

class 컴포넌트 - @observer Hooks 컴포넌트 - useObserver

위의 상황에 맞게 저것들을 꼭 가지고 있어야, observable이 바뀔 때마다 자동으로 반응이 일어난다!!


참고

  • 인프런 강의 (zerocho)