JS, TS deep immutable types 만들기

8716 단어 jstsjs

왜 궁금?

코딩을 하게되면 이 배열 또는 객체가 immutable하여 외부의 영향을 안받는다는 확신이 필요할 때가 있습니다.
그러기위해 저는 TS로 개발을 하면 동결을 위해 as const 구문을 많이 사용합니다. const assertion이라고 불리는 것으로
객체를 readonly 상태 즉 immutable한 객체로 만들어 줍니다.

하지만 컴파일 타임에만 해당하는 것이고 JS가 된 파일을 보면 일반 객체로 풀려있는 것을 발견할 수 있습니다.

목적은 런타임에도 객체 동결을 만드는 것입니다.

JS, Object.freeze

하지만 이미 JS에는 Object.freeze라는 훌륭한 객체 메소드가 있습니다.

얕은 동결

obj = {
  internal: {}
};

Object.freeze(obj);
obj.internal.a = 'aValue';

obj.internal.a // 'aValue'

하지만 객체의 깊이 1 까지만 동결을 해주기 때문에 재귀 함수를 통해 객체 내부 전체를 동결 시켜보도록 하겠습니다.

function deepFreeze<T>(obj: T) {
  var propNames = Object.getOwnPropertyNames(obj);
  for (let name of propNames) {
    let value = (obj as any)[name];
    if (value && typeof value === "object") {
      deepFreeze(value);
    }
  }
  return Object.freeze(obj);
};

const bill = deepFreeze({
  name: "Bill",
  profile: {
    level: 1,
  },
  scores: [90, 65, 80],
} as const);

이제 런타임에도 객체나 배열을 deep immutable 상태로 만들어 사용할 수 있게되었습니다.

Immutable Type

위의 방법들로 충분히 deep immtable이 되었습니다.
하지만 TS에는 타입 추론이라는 것이 존재하는데
명시적으로 bill이 동결상태라는 것을 어떻게 나타낼 수 있을까요?

type Person = {
  readonly name: string;
  readonly profile: {
    readonly level: number;
  };
  readonly scores: number[];
};

위와 같이 귀찮게도 모든타입에 readonly를 적어주어야 할까요?
운이 좋게도 재귀적인 type을 만들어 주면 그럴 필요가 없습니다.

...

type Immutable<T> = {
  readonly [K in keyof T]: Immutable<T[K]>;
};

const bill: Immutable<Person> = deepFreeze({
  name: "Bill",
  profile: {
    level: 1,
  },
  scores: [90, 65, 80],
});

위와 같이 사용하여 런타임에도 컴파일 타임에도 명시적으로 이 객체는 동결 되었다는 것을 알 수 있게 되었습니다.

좋은 웹페이지 즐겨찾기