개발 기술/타입스크립트

[ts] 리터럴 타입으로 추론해줘!(with. const, as const, Object.freeze)

by GicoMomg 2023. 12. 12.

1. 리터럴 타입으로 추론된다고?

  • 타입스크립트는 타입을 명시적으로 지정하는 방법과 암시적으로 지정하는 방법이 있다.
  • 명시적인 타입 지정은, 선언된 타입으로 변수 타입을 지정하는 방법이다.
// 선언된 타입
type AnimalType = "cat" | "dog";

// 선언된 타입으로 지정
const animal:AnimalType = "cat";

  • 암시적 타입 지정은 타입을 지정하지 않아도, 선언시 초기화를 하면 초기값으로 타입을 추론하는 걸 말한다.
  • 아래와 같이 animal 변수를 문자열(“cat”)로 초기화하자, 타입이 string으로 추론 되었다.


  • 그런데 만약 animal 변수가 상수라면, 타입이 무엇으로 추론될까?
  • 신기하게도 string이 아닌, 값(”cat”)을 타입으로 추론한다!


🤔 왜 const로 선언하면 값(리터럴)를 타입으로 가지는 걸까?
그리고 객체도 const로 선언하면 값을 타입으로 가질까?

  • 이번시간에는 리터럴 타입으로 추론되는, const, as const, Object.feeze에 대해 알아보았다.




2. 리터럴 타입으로 추론된다!

1) const로 리터럴 타입 추론 유도하기

  • 우리는 원시형 데이터는 let, const로 선언할 수 있다.
  • 그리고 const로 선언된 변수는, 리터럴() 타입으로 추론 된다.
let animal = "cat";   // 타입이 string

const ANIMAL = "cat";  // 타입이 "cat"

🙄 아니 선언 방식에 따라 타입 추론이 달라진다고? 왜 그런거지?

  • 타입스크립트는 값 불변성이 보장되면, 구체적인 유형으로 타입을 추론할 수 있다.
  • const로 변수를 선언하면 값 수정이 불가해 불변성이 보장된다.
  • 그래서 const로 선언된 원시형 데이터는 리터럴 타입으로 추론되는 것이다.

😆 그럼 객체도 const로 선언하면, 리터럴 타입으로 추론되는 걸까?

  • None! 객체는 const로 선언해도 리터럴 타입으로 추론되지 않는다.
  • 그럼, 객체는 어떻게 선언해야 리터럴 타입으로 추론될까?



2) 객체의 리터럴 타입 추론을 유도하는 2가지 방법

  • 타입스크립트는 값의 불변성이 보장된 경우 리터럴 타입으로 추론한다.
  • 그러나 객체는 참조타입이라, const로 선언해도 추론 범위가 한정되지 않고 값을 변경할 수 있다.
  • 그렇다면, 어떻게 해야 객체를 리터럴 타입으로 추론할까? 바로 as const를 쓰는 것이다!

(1) as const로 리터럴 타입 추론 유도하기

  • as const는 타입스크립트에서 제공하는 문법이다.
  • 이 문법은 객체나 배열을 불변(immutable)으로 만들기에, 객체의 내부 값이 readonly 처리가 된다.
const animals = ["Cat", "Dog", "Cheetah"] as const;
// `animals`는 다음의 타입으로 추론됨

const animals: readonly ["Cat", "Dog", "Cheetah"]

  • 또한 as const로 선언된 객체는 값 변경시 타입스크립트 에러가 발생한다.


💡 배열이 아닌 객체에서는 as const는 어떻게 동작할까?

  • 만약 animalTypeMap 이라는 객체가 있다고 가정해보자.
  • 이 데이터는 동물의 이름별 동물 유형을 저장하고 있다.
const animalTypeMap = {
  lili: 'cat',                    
  buge: 'dog',                
};

// 속성값 변경 가능
animalTypeMap.lili = "crocodile";    

  • 이 데이터를 const로 선언하면, string 객체로 타입이 추론된다.
// 추론 형태

const animalTypeMap: {
  lili: string;
  buge: string;
}

  • 하지만 as const를 쓰면…
const animalTypeMap = {
  lili: 'cat',                
  buge: 'dog',            
} as const;

  • 각 속성이 읽기 전용 처리가 된, 리터럴 타입으로 추론된다!
// 추론 형태

const animalTypeMap: {
  readonly lili: "cat"; 
  readonly buge: "dog";
}

(2) Object.freeze로 리터럴 타입 추론 유도하기

🤔 잠깐! 객체가 불변값을 가질 때 리터럴 타입으로 추론된다고?
그렇다면 객체의 속성 변경을 막는 Object.freezeas const를 대체할 수 없을까?

  • Object.freeze는 객체의 변경을 막을 수 있는 함수이다.
  • Object.freeze 처리를 한 객체는 속성 추가, 제거는 물론 변경도 안된다.
const animalTypeMap = Object.freeze({
  lili: "cat",
  buge: "dog",
});

animalTypeMap1.lili = 44;   // 변경 ❌
animalTypeMap1.tata = 11;   // 추가 ❌
delete animalTypeMap1.lili; // 삭제 ❌

  • 타입은 아래와 같이 읽기 전용 객체로 추론된다.
// 추론 형태

const animalTypeMap: Readonly<{
    lili: "cat";
    buge: "dog";
}>

  • as const와 다른 점은 첫번째, Object.freeze는 얕은 freeze라 중첩 객체의 값을 변경할 수 있다.
  • 그래서 중첩 객체의 불변성을 보장할 수 없다. (Object.seal도 알고 싶다면 요기!)
const animalTypeMap = Object.freeze({
  lili: "cat",
  buge: "dog",
  tata: { age: 12 }
});

animalTypeMap.tata.age = 55;  // 수정 가능
const animalTypeMap = {
  lili: "cat",
  buge: "dog",
  tata: { age: 12 }
} as const;

animalTypeMap.tata.age = 55;  // ⚠ Error(TS2540) 읽기 전용 속성이므로 age 에 할당할 수 없습니다.

  • 두 번째, Object.freeze는 런타임에서 객체의 속성 변경을 막을 수 있다.
  • 그에 반해 as const는 JavaScript 런타임에는 없는 개념이라, as const로 선언한 객체는 런타임에서 값을 변경할 수 있다.
  • as constObject.freeze의 차이점을 정리하면 다음과 같다.
Object.freeze as const
정의 - 속성 추가, 제거, 편집을 방지
- 프로토타입이 변경도 방지
- 객체를 불변(읽기전용)으로 만듬
- 다른 리터럴에 적용시 타입 확장을 방지함
에러 감지 시점 - 컴파일, 런타임에서 객체의 값 수정시 에러 감지 - 타입과 다른 값 지정시 컴파일에서 에러 감지
중첩 객체 변경 - 중첩 객체의 값 변경이 가능 - 중첩 객체의 값 변경이 불가

❓ 그럼 어떤 경우에 as const, Object.freeze를 써야할까?

  • 객체의 내부 속성은 물론 중첩 객체의 변경도 막고 싶다면? → as const
  • 구체적인 리터럴 타입 추론을 하고 싶다면? → as const
  • 런타임에서 객체의 속성 변경을 막고 싶다면? → Object.freeze




3. 마치며…

이번시간에는 타입스크립트의 리터럴 타입 추론에 대해 알아보았다.

타입스크립트은 타입을 명시하지 않아도, 초기값으로 타입을 추론한다. 그리고 상수로 선언된 변수는 리터럴 타입으로 추론되었다. 단, 객체는 참조형이라 리터럴 타입으로 추론하려면 as const를 사용해야 했다.

이외에도 as const와 유사한 Object.freeze가 있는데, 값 변경을 막을 수 있지만 중첩 객체의 변경은 막을 수 없었다. 대신 런타임에서 객체의 값 변경을 막을 수 있는 장점이 있었다.

만약 리터럴 타입 추론을 쓰고 싶다면, 윈시 데이터는 const를, 참조형 데이터는 as constObject.freeze를 써보자!

반응형

댓글