Advanced State Management

Contents

  • MobX

    • Computed Property (getter, setters)
    • Decorator

MobX

Computed Property (getter, setters)

Computed property는 엄밀히 따지면 ES5에 포함된 문법입니다. 하지만 ES6의 Computed property를 사용하면 좀 더 간편하게 사용할 수 있습니다.

// 일반적인 class와 method
class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const person = new Person('Grace', 'Hopper');

person.firstName; // 'Grace'
person.lastName; // 'Hopper'
person.fullName(); // 'Grace Hopper'

Computed property를 사용하면 아래 예시의 Person class의 consumer가 instance의 interface에 신경쓰지 않고 사용할 수 있습니다. 일관된 interface가 그렇지 않은 경우보다 일반적으로 더 나은 경우가 많다고 합니다.

// ES6
class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const person = new Person('Grace', 'Hopper');

person.firstName; // 'Grace'
person.lastName; // 'Hopper';
person.fullName; // 'Grace Hopper'

ES5에서도 computed를 사용할 수 있지만, 아래와 같이 깔끔한 모습이 아니기 때문에 아마도 많이 보지는 못 했을 것입니다.

// ES5
function Person(firstName, lastName) {
  this.fistName = firstName;
  this.lastName = lastName;
}

Object.defineProperty(Person.prototype, 'fullName', {
  get: function() {
    return this.firstName + ' ' + this.lastName;
  }
});

Decorator

Decorator는 기본적으로 higher-order function의 기능을 하지만, syntactic sugar를 제공합니다. 우선 앞서 사용했던 defineProperty를 다시 살펴보겠습니다. defineProperty에 인자로 타켓 객체, key 그리고 descriptor를 전달합니다.

만약 decorator를 직접 만들고자 한다면 아래와 같이 함수로 작성할 수 있습니다.

function decoratorName(target, key, descriptor) {
  //...
}

예시로 readonly란 함수를 만들어 볼 수 있습니다.

function readonly(target, key, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

그리고 이것을 아래와 같이 decorator로 사용할 수 있습니다. 프로그래밍을 할 때는 'core-decorators'나 'lodash-decorator'등 라이브러리가 많기 때문에 실제로 구현을 해서 사용하는 경우는 드물 것입니다.

class Person {
  constructor(firstName, lastName) {
    this.fistName = fistName;
    this.lastName = lastName;
  }

  @readonly get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

MobX

MobX의 주요 개념은 상태를 단순한 data structure에 담아두고, MobX에게 상태의 갱신을 위임한다는 것입니다. 기본 개념에 대한 다른 포스트도 있으니 참고해주세요.

우선 MobX를 이용해서 Person class를 아래와 같이 만들 수 있습니다.

const { computed, action, observable, autorun } = mobx;

class Person {
  @observable firstName;
  @observable lastName;

  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  @computed get fullName() {
    return this.firstName + ' ' + this.lastName;
  }
}

const person = new Person('Eric', 'Kim');
const quote = observable('Only Javascript can prevent forest fires.');

const render = () => {
  document.body.innerText = person.fullName + ' : ' + quote;
};

autorun(render);

observable decorator를 사용해 우리는 해당 값이 변경되면 MobX를 통해 변경된 사실을 인지할 수 있습니다. 맨 밑의 autorun은 MobX가 관찰하고 있는 상태가 변경될 때마다 호출이 됩니다. 공식 문서 상 autorun은 함수를 자동으로 호출해야 하지만, 새로운 값을 반환하지 않는 함수에 적용하는것이 좋다고 소개합니다. 새로운 값을 생성해야 한다면, computed를 사용할 수 있습니다.