TIL 76 | MobX Basics

hyounglee·2020년 11월 2일
0

JavaScript

목록 보기
30/31

갓 벨로퍼트님 빛만 따라 갑니다...✨
몹엑스 응용 실습 CodeSandbox 자료

MobX

리덕스와 같이 인기있는 리액트 상태 관리 라이브러리다.

주요 개념

1. Observable State (관찰받는 상태)

MobX를 사용하는 앱의 상태는 Observable하다. 앱에서 사용하고 있는 상태는 변화할 수 있고, 특정 부분이 바뀌는 경우 MobX에서는 어떤 부분이 바뀌었는지 알 수 있다.

2. Computed Value (연산된 값)

기존의 상태값과 다른 연산된 값에 기반하여 만들어질 수 있는 값. 주로 성능 최적화를 위해 많이 사용된다. 연산에 기반되는 값이 바뀔 때만 새로 연산하게 하고, 바뀌지 않았다면 기존의 값을 사용하게 한다.

3. Reactions (반응)

Computed Value와 비슷한데, 연산된 값은 우리가 특정 값을 연산할 때만 처리가 되는 반면 Reactions는 값이 바뀜에 따ㅏ라 할 일을 정하는 것을 의미한다.

4. Actions (행동)

상태에 변화를 일으키는 것을 의미한다. Observable State에 변화를 일으키는 코드를 호출한다? 이것이 하나의 액션이 된다. 리덕스의 액션과는 다르게 객체의 형태로 만들어지지는 않는다.

예시 코드

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

// observable state
const calculator = observable({
  a: 1,
  b: 2
});

// 특정 값이 바뀔 때 특정 작업하기
reaction(
  () => calculator.a,
  (value, reaction) => {
    console.log(`a값이 ${value}로 바뀌었네요!`)
  }
)

reaction(
  () => calculator.b,
  (value, reaction) => {
    console.log(`b값이 ${value}로 바뀌었네요!`)
  }
)

// 연산된 값을 사용한다
const sum = computed(() => {
  console.log('계산중이예요!');
  return calculator.a + calculator.b;
})

sum.observe(() => calculator.a); // a 값을 주시
sum.observe(() => calculator.b); // b 값을 주시

calculator.a = 10;
calculator.b = 20;

// 여러 번 조회해도 computed 안의 함수 호출 하지 않는다.
console.log(sum.value);
console.log(sum.value);

// 내부 값이 바뀌면 다시 호출
calculator.a = 20;
console.log(sum.value);

autorun

autorun은 reaction 이나 computed의 observe 대신에 사용될 수 있는데, autorun으로 전달해주는 함수에서 사용되는 값이 있으면 자동으로 그 값을 주시하여 그 값이 바뀔 때마다 함수가 주시되도록 한다.

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

// Observable State 만들기
const calculator = observable({
  a: 1,
  b: 2
});

// computed 로 특정 값 캐싱
const sum = computed(() => {
  console.log('계산중이예요!');
  return calculator.a + calculator.b;
});

// **** autorun 은 함수 내에서 조회하는 값을 자동으로 주시함
autorun(() => console.log(`a 값이 ${calculator.a} 로 바뀌었네요!`));
autorun(() => console.log(`b 값이 ${calculator.b} 로 바뀌었네요!`));
autorun(() => sum.get()); // computed로 만든 값의 `.get` 함수를 호출해주면 하나하나 observe 하지 않아도 된다.

calculator.a = 10;
calculator.b = 20;

// 여러번 조회해도 computed 안의 함수를 다시 호출하지 않지만..
console.log(sum.value);
console.log(sum.value);

calculator.a = 20;

// 내부의 값이 바뀌면 다시 호출 함
console.log(sum.value);

class 문법을 사용해보기

class로 장바구니를 구현하고, decorate 함수를 통하여 mobX를 적용한 코드이다.

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

class GS25 {
  basket = [];

  get total() {
    console.log('계산중입니다!');
    return this.basket.reduce((prev, curr) => prev + curr.price, 0);
  }

  select(name, price) {
    this.basket.push({name, price});
  }
}

// decorate: 각 값에 mobX 함수 적용
decorate(GS25, {
  basket: observable,
  total: computed,
  select: action, // action을 사용하면 좋은 점?
});

const gs25 = new GS25();
autorun(() => gs25.total);
gs25.select('물', 800);
console.log(gs25.total);
gs25.select('물', 800);
console.log(gs25.total);
gs25.select('포카칩', 1500);
console.log(gs25.total);

action을 사용하면, 나중에 개발자도구에서 변화의 세부 정보를 볼 수 있고, 변화를 한꺼번에 일으켜서 변화가 일어날 때마다 reaction들이 나타나는 것이 아니라 모든 액션이 끝나고 난 다음에야 reaction이 나타나게끔 한다는 장점이 있다.

액션을 한꺼번에 일으키는 것은 transaction을 통해 할 수 있다.

transaction

**transaction을 적용하지 않았을 때
**

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

class GS25 {
  basket = [];

  get total() {
    console.log('계산중입니다!');
    return this.basket.reduce((prev, curr) => prev + curr.price, 0);
  }

  select(name, price) {
    this.basket.push({name, price});
  }
}

// decorate: 각 값에 mobX 함수 적용
decorate(GS25, {
  basket: observable,
  total: computed,
  select: action,
});

const gs25 = new GS25();
autorun(() => gs25.total);
// 새 데이터 추가될 때 알림
autorun(() => {
  if (gs25.basket.length > 0) {
    console.log(gs25.basket[gs25.basket.length - 1])
  }
})

gs25.select('물', 800);
gs25.select('물', 800);
gs25.select('포카칩', 1500);

console.log(gs25.total);

Console

계산중입니다..! 
계산중입니다..! 
Object {name: "물", price: 800}
계산중입니다..! 
Object {name: "물", price: 800}
계산중입니다..! 
Object {name: "포카칩", price: 1500}
3100

transaction을 적용했을 때

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

class GS25 {
  basket = [];

  get total() {
    console.log('계산중입니다!');
    return this.basket.reduce((prev, curr) => prev + curr.price, 0);
  }

  select(name, price) {
    this.basket.push({name, price});
  }
}

// decorate: 각 값에 mobX 함수 적용
decorate(GS25, {
  basket: observable,
  total: computed,
  select: action,
});

const gs25 = new GS25();
autorun(() => gs25.total);
// 새 데이터 추가될 때 알림
autorun(() => {
  if (gs25.basket.length > 0) {
    console.log(gs25.basket[gs25.basket.length - 1])
  }
})

transaction(() => {
  gs25.select('물', 800);
  gs25.select('물', 800);
  gs25.select('포카칩', 1500);
})

console.log(gs25.total);

Console

계산중입니다..! 
계산중입니다..! 
Object {name: "포카칩", price: 1500}
3100

transaction을 통해 계산 작업은 처음 한번, 그리고 transaction이 끝나고 한번 호출이 되었고, 새 데이터가 추가될 때마다 알리는 부분도 3개를 다 추가하고 나서 한번만 찍히는 것을 볼 수 있다. 이렇게 액션을 잘 사용하면 성능 개선을 이뤄낼 수 있고 개발자 도구를 사용할 때 변화에 대한 자세한 정보를 알 수 있다.

decorator 문법

decorator 문법은 정규 문법은 아니지만 바벨 플러그인으로 사용할 수 있는 문법이다. 이 문법을 사용하면 decorate 함수가 필요없다.

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

class GS25 {
  @observable basket = [];

  @computed
  get total() {
    console.log('계산중입니다!');
    return this.basket.reduce((prev, curr) => prev + curr.price, 0);
  }
  @action
  select(name, price) {
    this.basket.push({name, price});
  }
}

const gs25 = new GS25();
autorun(() => gs25.total);
// 새 데이터 추가될 때 알림
autorun(() => {
  if (gs25.basket.length > 0) {
    console.log(gs25.basket[gs25.basket.length - 1])
  }
})

transaction(() => {
  gs25.select('물', 800);
  gs25.select('물', 800);
  gs25.select('포카칩', 1500);
})

console.log(gs25.total);
profile
(~˘▾˘)~♫•*¨*•.¸¸♪ ❝ 쉽게만 살아가면 재미없어 빙고 .ᐟ ❞

0개의 댓글