< React(리액트) : 상태관리 라이브러리 MobX 개념 >
* 개념 학습 기본 code
; action은 observable 요소 값을 변경시킴.
; transaction의 parameter로 action 함수 여러개로 구성된 함수를 전달하면, 전달된 action들의 집합 전체가 반영된 후, MobX의 주시 함수들이 실행됨.
; action이 observable 요소 값을 변경하면, computed는 observe를 사용해 요소 값의 변화를 관찰하고, computed 결과 값이 계산되어 cache에 저장됨.
; action이 observable 요소 값을 변경하면, 요소 값을 주시하도록 지정된 reaction이 발동되어 reaction의 parameter로 지정된 함수가 실행됨.
; autorun은 요소값을 observe로 주시하지 않아도 autorun 내부에 지정된 함수는 자동으로 요소값을 주시하다가 변경이 발생하면 지정된 함수가 실행됨.
; @를 사용하여 변수, 함수 등을 decorate 시켜주면(@observable, @computed, @action 등), observable, computed, action 속성을 간단히 적용시킬 수 있음.
import { observable, reaction, computed, autorun } from 'mobx';
// observable() 함수의 parameter 로 주시당할 생성 객체를 전달
// 생성된 주시당할 객체는 const 로 할당
const calculator = observable({
a: 0,
b: 0
})
// reaction() 함수로 특정 값을 주시값 변경시 특정 함수 실행
// reaction() 함수의 첫번째 parameter는 주시 값 지정 함수, 두번째 parameter는 실행함수를 전달
reaction(
() => calculator.a,
(value, reaction) => {
console.log(`a 값이 ${value} 로 바뀌었네요!`);
}
);
reaction(
() => calculator.b,
value => {
console.log(`b 값이 ${value} 로 바뀌었네요!`);
}
);
// computed() 함수로 특정값(sum) 캐싱해서 const 로 할당
// computed() 함수의 parameter는 연산 결과를 return 하는 함수
// observe() 함수를 이용해서 computed 결과값이 연산 요소값을 주시하도록 지정
const sum = computed(
() => {
console.log(`computed 연산이 실행됩니다. a: ${calculator.a}, b: ${calculator.b}`);
return calculator.a + calculator.b;
}
);
sum.observe(
() => calculator.a
);
sum.observe(
() => calculator.b
);
// **** autorun 은 함수 내에서 조회하는 값을 자동으로 주시함.
// computed로 만들어진 sum의 내부 요소는 자동 주시함.
autorun(() => console.log(`오토런 : a 값이 ${calculator.a} 로 바뀌었네요!`));
autorun(() => console.log(`오토런 : b 값이 ${calculator.b} 로 바뀌었네요!`));
autorun(() => console.log(`sum.get 값 : ${sum.get()}`));
// 값이 최초로 변경되는 순간 값이 변경되기 전 기준으로 computed 함수가 최초 실행된 후
// 변경된 값 기준으로 다시 실행됨.
calculator.a = 10;
calculator.b = 20
// **** 여러번 조회해도 computed 안의 함수를 다시 호출하지 않지만..
console.log(sum.value);
console.log(sum.value);
// 내부의 값이 바뀌면 다시 호출 함
calculator.a = 20;
console.log(sum.value);
* 실행 결과
computed 연산이 실행됩니다. a: 0, b: 0
computed 연산이 실행됩니다. a: 10, b: 0
computed 연산이 실행됩니다. a: 10, b: 20
computed 연산이 실행됩니다. a: 20, b: 20
* mobx 를 활용한 counter 기본 예제 code
-- 앱 생성 및 관련 라이브러리 설치 --
$ npm create-react-app mobx_std
$ cd mobx_std
$ yarn add mobx mobx-react
$ yarn start
-- @(decorator) 를 사용하려면 babel 설정을 커스터마이징해야 함. --
프로젝트 eject 를 해야 babel 설정을 변경할 수 있음.
1. 프로젝트 eject
$ yarn eject
$ yarn # 가끔 일부 모듈이 설치되지 않는 증상, 이걸 한번 더 실행해서 확실하게 설치
$ yarn add babel-preset-mobx
2. babel 설정 커스터마이징
package.json - babel 부분 수정
"babel": {
"presets": [
"react-app",
"mobx"
]
},
3. 서버 재실행
yarn start
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();
//App.js
import React, { Component } from 'react';
import Counter from './Counter';
class App extends Component {
render() {
return (
<div>
<Counter />
</div>
);
}
}
export default App;
//Counter.js
import React, {Component} from 'react'
import {observable, action} from 'mobx'
import {observer} from 'mobx-react'
@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>
);
}
}
export default Counter;
* mobx 를 활용한 counter 기본 예제 code에 스토어 적용
// src/stores/CounterStore.js
// 스토어 생성하기
import { observable, action } from 'mobx';
export default class CounterStore {
@observable number = 0;
@action increase = () => {
this.number++;
}
@action decrease = () => {
this.number--;
}
}
// src.index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'mobx-react';
// MobX 에서 사용하는 Provider
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import CounterStore from './stores/counterStore';
// 스토어 불러옵니다.
const counterStore = new CounterStore();
// 스토어 인스턴스를 만들고
ReactDOM.render(
<Provider CounterStore={counterStore}>
{/* Provider 에 위에서 만든 스토어 instance를 props 로 넣어줍니다.
이렇게 App component로 전달된 props는 App의 자식 component가
@inject('CounterStore') 로 가지고 와서 this.props.CounterStore로
사용하거나,@inject(stores => ({
number:.... 형태로 스토어의 구성 요소별로 필요한 요소만 가지고와서
this.props.number 형태로 개별적으로 사용할 수 있습니다.
전자는 Counter.js, 후자는 Counter2.js 참고 */}
<App />
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
// src/App.js
/* App component는 index.js 에서 Provider Wrapper 를 통해 props로 전달 받은
스토어를 가지고 있으며, App 의 자식 component 들은 inject decorator를 사용하여
전달받은 스토어를 자신의 component에 주입하여 사용할 수 있다.*/
import React, { Component } from 'react';
import Counter from './Counter';
import Counter2 from './Counter2';
class App extends Component {
render() {
return (
<div>
<Counter />
<Counter2 />
</div>
);
}
}
export default App;
// src/Counter.js
// 스토어 전체를 inject 해서 사용하는 방법
import React, { Component } from 'react';
import { observer, inject } from 'mobx-react';
@inject('CounterStore')
@observer
class Counter extends Component {
render() {
const { CounterStore } = this.props;
return (
<div>
<h1>{CounterStore.number}</h1>
<button onClick={CounterStore.increase}>+1</button>
<button onClick={CounterStore.decrease}>-1</button>
</div>
);
}
}
export default Counter;
// src/Counter2.js
// 스토어 중 필요한 개별요소별로 inject 해서 사용하는 방법
import React, { Component } from 'react';
import { observer, inject } from 'mobx-react';
// **** 함수형태로 파라미터를 전달해주면 필요한 특정 값만 받아올 수 있음.
@inject(stores => ({
number: stores.CounterStore.number,
increase: stores.CounterStore.increase,
decrease: stores.CounterStore.decrease,
}))
@observer
class Counter2 extends Component {
render() {
const { number, increase, decrease } = this.props;
return (
<div>
<h1 style={{color: "red", backgroundColor: "green"}}>{number}</h1>
<button onClick={increase}>+1</button>
<button onClick={decrease}>-1</button>
</div>
);
}
}
export default Counter2;