@computed


getter함수에만 사용이 가능한, @observable과 유사한 기능을 갖습니다.


* Javascript es6 의 getter를 모르시는 분들은 이곳을 참고해 주세요.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/get


하지만 observable과 가장 큰 차이점은 최적화의 문제입니다.


observable은 값이 변경될때마다 무조건 렌더링을 다시 하지만,


실제 사용할 값을 computed해놓고 사용한다면, observable값이 변경되더라도, computed 에 명시된 값에 부합하지 않다면, 재 렌더링 하지 않습니다.


사용법은 역시 함수로 사용하는 방법과 데코레이터를 사용하는 방법이 존재합니다.


이번포스팅에서 부터는 함수로 사용하는 방법에 대해서는 예제를 만들지 않겠습니다.

( 거의 사용 하지 않습니다. 특히 타입스크립트에서는.. )


데코레이터를 사용하는 예제를 작성해 보도록 하겠습니다.


이전에 사용하던 프로젝트의 AgeStore.tsx 파일에 computed를 추가하겠습니다.


import { observable, computed } from 'mobx';

export default class AgeStore {
@observable
private _age: number = 30;

constructor(age: number) {
this._age = age;
}

@computed
get age(): number {
return (this._age > 40) ? this._age : 0;
}
public getAge(): number {
return this._age;
}
public setAge(age: number): void {
this._age = age;
}

}


만약 this._age가 40이 넘는다면 렌더링을 하고 그렇지 않으면 0을 렌더링 할것입니다.


그리고 age 컴포넌트를 변경하겠습니다.

<h1>{ageState.age}</h1>


이전에는 getAge() 를 렌더링 했지만 이번에는 computed로 지정한 age를 렌더링 합니다.


실행해 보겠습니다.



실행하였을때 0이 나오는것을 볼수 있습니다.

왜냐하면 실행하였을때 this._age의 값은 30이니까요.


여기서 나이증가 버튼을 계속 누르다 보면, 


짠 렌더링이 실시됩니다.


즉 this._age값은 계속 변화하였지만 computed로 인해 렌더링이 되지 않고 값만 변화하였다가

40이 넘어가는 순간부터 렌더링을 실시하게 된것입니다.


예제에서는 단순하게 하였지만, 좀더 정밀하게 구성한다면, 렌더링을 최적화 하는데 매우 큰 도움이 될것으로 예상됩니다.


* 만약 setter 함수를 사용하여 값을 변화시키고, 같은 이름의 getter 함수를 computed 해놓았다면,

setter 함수 호출후에는 반드시 getter 함수가 호출됩니다. 

하지만 이때 반드시 getter 함수 정의후 setter 함수를 정의 하여야 합니다.


https://mobx.js.org/refguide/computed-decorator.html


@action - 추후에 내용을 보강할 계획입니다.


action은 state를 수정하는 함수입니다. ( observable이 있는 프로퍼티를 수정 )

옵셔널한 기능이므로 사용해도, 안해도 됩니다.

하지만 mobx의 use-strict 모드를 사용한다면, state를 변경하는 함수에는 반드시 사용해야 합니다.


예시로 살펴보겠습니다.


우선 src/index.tsx에 use-strict 모드를 적용하겠습니다.


import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { App } from './containers';
import registerServiceWorker from './registerServiceWorker';
import './index.css';

import { useStrict } from 'mobx';

useStrict(true);

ReactDOM.render(
<App />,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();


그리고 실행하면 컴파일에서는 오류가 전혀 발생하지 않습니다.


그러나 실행화면에서 버튼을 눌러 state를 변경하려고 하는 순간



에러를 발생시킵니다.


대충 봐도 action을 쓰라고 써있습니다.


그렇다면 action 을 사용해야겠습니다.


AgeStore.tsx 파일에 적용하겠습니다.


import { observable, computed, action } from 'mobx';


export default class AgeStore {
@observable
private _age: number = 30;

constructor(age: number) {
this._age = age;
}

@computed
get age(): number {
return (this._age > 40) ? this._age : 0;
}
public getAge(): number {
return this._age;
}
@action
public setAge(age: number): void {
this._age = age;
}

}


간단하게 observable된 프로퍼티 값을 바꾸는 함수에 @action을 추가하면 됩니다.


https://mobx.js.org/refguide/action.html


@inject와 Provider


이전에 Redux에서 쓰던 Provider와 똑같이 사용합니다.

다른점은, 이전에 redux에서는 provider로 넘긴 props를 사용하기 위해서는

컴포넌트를 컨네이터로 감싼뒤 mapStateToProps, mapDispatchToProps를 작성하여 사용가능하게 만들었습니다. 하지만 mobx에서는 @inject 선언으로 사용이 가능합니다. 심플하게..


예제를 만들어 보겠습니다.


src/index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { App } from './containers';
import registerServiceWorker from './registerServiceWorker';
import './index.css';

import { useStrict } from 'mobx';
import { Provider } from 'mobx-react';
import { AgeStore } from './stores';

useStrict(true);

const ageState = new AgeStore(30);

ReactDOM.render(
<Provider store = {ageState}>
<App />
</Provider>,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();


AgeStore를 Provider를 통해 props로 내려주겠습니다.


그리고 Age 컴포넌트에서 사용해 보겠습니다.


src/components/Age.tsx

import * as React from 'react';

import { observer, inject } from 'mobx-react';

import { AgeStore } from '../stores';

@inject('store')
@observer
class Age extends React.Component<{ store?: AgeStore; }, {}> {
constructor(props: {}) {
super(props);
}

componentWillReact() {
console.log('componentWillReact');
}
componentWillUpdate() {
console.log('componentWillUpdate');
}
componentDidUpdate() {
console.log('componentDidUpdate');
}

render() {
console.log('render');
const store = this.props.store as AgeStore;
return (
<div className="Age">
<h1>{store.age}</h1>
<button onClick={() => store.addAge()}>나이증가</button>
</div>
);
}
}

export default Age;


정말 간단하게 @inject('store') 명령으로 가져오는것이 가능합니다.


하지만 여기서 중요한것은 typescript이므로 props의 타입을 지정해줘야 하는데 

stroe를 타입으로 지정해주면 됩니다.

또한 사용하기 위해서는

const store = this.props.store as AgeStore;

처럼 명시해주어야 사용이 가능합니다.


* 저는 이번 예제를 좀더 깔끔하게 사용 하기 위해 addAge함수를 store에 넣었습니다.

import { observable, computed, action } from 'mobx';

export default class AgeStore {
@observable
private _age: number = 30;

constructor(age: number) {
this._age = age;
}

@computed
get age(): number {
return (this._age > 40) ? this._age : 0;
}
public getAge(): number {
return this._age;
}
@action
public setAge(age: number): void {
this._age = age;
}
@action
public addAge(): void {
this._age = this._age + 1;
}

}



autorun


autorun은 observers 자체를 가지지 않는 리액션 함수를 만들고자하는 경우에 사용할 수 있습니다. 

일반적으로 로깅, 지속성 또는 UI 업데이트 코드와 같이 반응적인 코드에서 명령형 코드로 연결해야하는 경우입니다. autorun과 computed는 비슷하게 보이지만, 완전히 다르게 동작합니다.

autorun을 사용하면, 종속성 중 하나가 변경 될 때마다 무조건 다시 트리거 되는 반면에,

computed는 상황에 따라 트리거 됩니다.


따라서 자동으로 실행되어야 하지만, 새로운 값을 결과로 내놓지 않는 함수가 있다면 autorun을 사용하고, 그렇지 않다면 computed를 사용하는 것이 좋습니다.

* 그래서 autorun 은 거의 대부분 로그를 출력하는데 이용됩니다.


예제를 통해 살펴보겠습니다.


src/stores/AgeStore.tsx

import { observable, computed, action, autorun } from 'mobx';

export default class AgeStore {
@observable
private _age: number = 30;

constructor(age: number) {
this._age = age;
}

@computed
get age(): number {
return (this._age > 40) ? this._age : 0;
}
public getAge(): number {
return this._age;
}
@action
public setAge(age: number): void {
this._age = age;
}
@action
public addAge(): void {
this._age = this._age + 1;
}

public ageLog(): Function {
return autorun(() => {
if (this.getAge() > 50) {
window.alert('나이가 너무 많아');
}
console.log('Age', this.getAge());
});
}

}


age 로그라는 함수를 만들어 autorun 함수를 return 하게 하였습니다.

여기서는 getAge하였는데 그 값이 50 이상일 경우에는 경고창을 띄우도록 하였고,

그렇지 않으면 콘솔을 띄우도록 하였습니다.


src/components/Age.tsx

import * as React from 'react';

import { observer, inject } from 'mobx-react';

import { AgeStore } from '../stores';

@inject('store')
@observer
class Age extends React.Component<{ store?: AgeStore; }, {}> {

private store = this.props.store as AgeStore;

constructor(props: {}) {
super(props);
this.store.ageLog();
}

componentWillReact() {
console.log('componentWillReact');
}
componentWillUpdate() {
console.log('componentWillUpdate');
}
componentDidUpdate() {
console.log('componentDidUpdate');
}

render() {
console.log('render');
return (
<div className="Age">
<h1>{this.store.age}</h1>
<button onClick={() => this.store.addAge()}>나이증가</button>
</div>
);
}
}

export default Age;


그리고 Age컴포넌트에서는 생성자 함수에서 ageLog를 실행하도록 하였습니다.


프로젝트를 실행해보겠습니다.



실행후 버튼을 누르면, age가 50이 되기 전까지는 증가된 값이 그냥 콘솔만 표시됩니다.

( 물론 이전에 computed때문에 렌더링은 되지 않습니다.)


하지만 50이 넘어가는 순간 경고창을 띄웁니다.


이런식으로 autorun을 사용하게 되는데, 경고창이 아닌 에러를 발생시켜 다른 작업을 할수도 있고,


활용법은 많을것으로 예상됩니다.


https://mobx.js.org/refguide/autorun.html



이번 포스팅까지가 mobx의 포스팅였습니다.

아마 다음 포스팅까지는 조금 시간이 걸릴듯 합니다.


남은 포스팅은 todoApp 예제와, react 서버사이드 렌더링 구현,  테스팅, nextjs 가 남아있는데,


그 전에 선수지식이 필요할듯 하여 좀더 공부를 한 뒤 포스팅을 할 예정입니다.


빠르게 공부하여 다시 react-typescript의 포스팅을 이어가도록 하겠습니다.


감사합니다.


이번 포스팅의 소스코드입니다.

https://github.com/JaroInside/tistory-react-typescript-study/tree/14.mobx-part3



참고 슬라이드 - http://slides.com/woongjae/react-with-typescript-4#/
참고 동영상 - https://www.youtube.com/playlist?list=PLV6pYUAZ-ZoHx0OjUduzaFSZ4_cUqXLm0

'React > React&typeScript' 카테고리의 다른 글

14.mobx-part2  (0) 2017.08.01
14.mobx-part1  (5) 2017.07.28
13. redux - part5  (0) 2017.07.26
13. redux - part4  (0) 2017.07.26
13. redux - part3  (0) 2017.07.25
블로그 이미지

Jaro

대한민국 , 인천 , 남자 , 기혼 , 개발자 jaro0116@gmail.com , https://github.com/JaroInside https://www.linkedin.com/in/seong-eon-park-16a97b113/

,