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적으로 사용할 것인지,
- ContextAPI를 통하여 묶어 주어 사용할 것인지
선택하여 사용할 수 있다.
결론
class 컴포넌트 - @observer Hooks 컴포넌트 - useObserver
참고
- 인프런 강의 (zerocho)