3. MobX 가 리액트를 만나면
MobX 는 리액트 종속적이진 않지만, 리액트에서 쓰려고 만들어졌기 때문에 함께 사용하면 엄청난 시너지가 발생합니다. 더 쉬운 글로벌 상태 관리는 물론이고, setState 도 쓸 필요가 없게 됩니다.
우리가 이전 섹션에서 decorator 문법을 통해서 더 편하게 MobX 를 사용하는 방법을 배웠었는데요, 우리가 만약에 create-react-app 으로 프로젝트를 만들면 기본적으로는 decorator 를 사용하지 못하기 때문에 따로 babel 설정을 해줘야 합니다.
우선, decorator 없이 리액트에서 MobX 를 사용하는 방법을 알아볼게요!
프로젝트 준비하기
다음 명령어를 통해 평범한 리액트 프로젝트를 만들어주세요.
$ npx create-react-app mobx-with-react
$ cd mobx-with-react
$ yarn add mobx mobx-react
$ yarn start
카운터 만들기
MobX 를 사용해서 엄청나게 간단한 카운터를 만들어보겠습니다.
src/Counter.js
import React, { Component } from 'react';
import { decorate, observable, action } from 'mobx';
import { observer } from 'mobx-react';
class Counter extends Component {
number = 0;
increase = () => {
this.number++;
};
decrease = () => {
this.number--;
};
render() {
return (
<div>
<h1>{this.number}</h1>
<button onClick={this.increase}>+1</button>
<button onClick={this.decrease}>-1</button>
</div>
);
}
}
decorate(Counter, {
number: observable,
increase: action,
decrease: action,
});
export default observer(Counter);
다 만드셨으면 App.js 에 반영해주세요.
src/App.js
import React, { Component } from 'react';
import Counter from './Counter';
class App extends Component {
render() {
return (
<div>
<Counter />
</div>
);
}
}
export default App;
카운터가 잘 작동하는지 확인해보세요.
진짜 간단하죠..? 뭔가 리액트스럽지가 않습니다. setState 없이 그냥 값 바꿔주면 알아서 렌더링해줍니다. 어떻게 알아서 렌더링해주냐구요? 코드 최하단에서 사용된 observer 가 observable 값이 변할 때 컴포넌트의 forceUpdate 를 호출하게 함으로써 자동으로 변화가 화면에 반영됩니다.
이게 성능상으로 과연 좋을까 걱정이 될 수도 있긴 하지만 놀랍게도 최적화가 많이 되어있어서 그 부분에 대해서는 크게 걱정하실 필요없습니다.
이런식으로, 리액트에서 MobX 를 사용할 땐 리덕스에서 했던 것 처럼 따로 다른 파일로 스토어를 만들 필요도 없고 (필요하면 만들 수도 있습니다) 그냥 컴포넌트에 바로 적용해줄 수 있습니다.
Decorator 와 함께 사용하기
여기서, decorator 를 사용하면 훨씬 더 편하게 문법을 작성 할 수 있는데요, 그러려면 babel 설정을 해주셔야 합니다. babel 설정을 커스터마이징 하려면 yarn eject 를 해야합니다.
eject 없이 하고 싶다면 customize-cra 를 참고하세요.
$ yarn add @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators
그리고 나서, package.json 을 열으신 다음에, babel 쪽을 찾아서 다음과 같이 수정해주세요.
"babel": {
"presets": [
"react-app"
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true}],
["@babel/plugin-proposal-class-properties", { "loose": true}]
]
}
설정을 다 하셨으면, 개발서버를 껐다 켜주시고, Counter 코드를 decorator 로 더 깔끔하게 작성해보겠습니다.
src/Counter.js
import React, { Component } from 'react';
import { observable, action } from 'mobx';
import { observer } from 'mobx-react';
// **** 최하단에 잇던 observer 가 이렇게 위로 올라옵니다.
@observer
class Counter extends Component {
@observable number = 0;
@action
increase = () => {
this.number++;
};
@action
decrease = () => {
this.number--;
};
render() {
return (
<div>
<h1>{this.number}</h1>
<button onClick={this.increase}>+1</button>
<button onClick={this.decrease}>-1</button>
</div>
);
}
}
// **** decorate 는 더 이상 필요 없어집니다.
// decorate(Counter, {
// number: observable,
// increase: action,
// decrease: action
// })
// export default observer(Counter);
// **** observer 는 코드의 상단으로 올라갑니다.
export default Counter;
어떤가요? decorator 문법을 사용하니까 조금 더 깔끔하지 않나요? 저는 그렇다고 생각하는데, 취향에 따라 싫을 수 도 있습니다. 우선 우리가 이 튜토리얼의 상단부에서 다뤘던것처럼 decorator 사용은 필수는 아니라는 점, 알아두세요!