JS에서 let과 const 의 가장 큰 차이점은 재할당 여부이다
그렇지만 TS에서는 또 하나의 차이점이 존재한다
바로 변수를 선언할 때 타입 추론이 다르게 일어난다는 것이다
let a = 'hi'; // let a: string
const b = 'hi'; // const b: "hi"
let a = 1; // let a: number
const b = 1; // const b: 1
그렇지만 배열,튜플,객체의 경우에는 let 과 const 모두
리터럴 타입으로 범위를 좁히지 않는다
let a = [1, 2, 3, 4] // let a: number[]
let b = [1, 'hi'] // let b: (string | number)[]
let c = {
num: 1,
str: 'hi',
}
// let c: {
// num: number;
// str: string;
// }
const a = [1, 2, 3, 4] // const a: number[]
const b = [1, 'hi']; // const b: (string | number)[]
const c = {
num: 1,
str: 'hi'
};
// const c: {
// num: number;
// str: string;
// }
const assertion은 말 그대로 상수라고 주장하는 것인데, 원래 상수가 아닌 것을 상수인 것으로 선언하는 기능이라고 유추 할 수 있다
<const>
를 사용하면 된다as const를 사용하게 되면 let으로 선언한 일반 변수에도 as const를 붙여서 동일하게 const assertion을 적용할 수 있다
let a = 'hi' as const; // let a: "hi"
let b = 1 as const; // let b: 1
이처럼 let에서 as const를 사용하게 되면 리터럴 타입이 적용되는 것을 볼 수 있다
그리고 타입이 리터럴 타입으로 고정되었기 때문에
다른 값으로 재할당이 불가능하다
그렇지만 이건 지양되는 방법이다
애초에 처음부터 let 대신 const를 사용하면 되기 때문이다
배열 , 튜플 , 객체의 경우 let 과 const 둘 다 리터럴 타입이 아닌
일반 타입으로 정의가 된다
let a = [1, 2, 3, 4] // let a: number[]
const a = [1, 2, 3, 4] // const a: number[]
그러므로 이런 Object 타입에다가 as const를 사용하게 되면
상수 형태로 값이 적용된다
또한 이렇게 as const로 객체의 각 키값에 타입을 리터럴 타입으로 고정함으로써
readonly가 자동으로 적용된다
let a = [1, 2, 3, 4] as const // let a: readonly [1, 2, 3, 4]
let b = [1, 'hi'] as const // let b: readonly [1, "hi"]
let c = {
num: 1,
str: 'hi',
} as const
// let c: {
// readonly num: 1
// readonly str: 'hi'
// }
const a = [1, 2, 3, 4] as const // const a: readonly [1, 2, 3, 4]
const b = [1, 'hi'] as const // const a: readonly [1, "hi"]
// const a = <const>[1, 'hi'];와 동일
const c = {
num: 1,
str: 'hi',
} as const
// const b: {
// readonly num: 1;
// readonly str: "hi";
// }
const assertion은 문자열이나 숫자, 배열이나 객체 리터럴 외에도
enum members, boolean에도 적용할 수 있다.
일반적으로 제네릭에다가 리터럴 타입을 넣을 때는
type arr = ['a', 'b', 'c']; // type alias
type First<T extends string[]> = T[0];
type head1 = First<arr>; // expected to be 'a'
let a: head1 = 'a'; // OK
let b: head1 = 'b'; // error
let arr = ['a', 'b', 'c']; // 일반 변수
type First<T extends string[]> = T[0]; // string[]의 [0]번째 타입이므로 string타입
// typeof arr은 리터럴 타입이 아닌 string[]타입이 됨
type head1 = First<typeof arr>; // expected to be string
let a: First<typeof arr> = 'a'; // OK
let b: First<typeof arr> = 'b'; // OK , 리터럴 타입이 아니므로 이것도 통과가 돼버림
let arr = ['a', 'b', 'c'] as const;
// ['a', 'b', 'c']의 [0]이므로 'a' 리터럴 타입이 반환 됨
type First<T extends readonly string[]> = T[0];
// typeof arr은 ['a', 'b', 'c'] 리터럴 타입
type head1 = First<typeof arr>; // expected to be 'a'
let a: head1 = 'a'; // OK
let b: head1 = 'b'; // error
이렇게 되는 이유는 TS는 타입이 런타임이 아닌 컴파일 타임에 결정이 된다
그러므로 type arr = ['a', 'b', 'c'] 같은 경우는 당연히 타입으로 지정한 거니까 컴파일 타임에도
이걸 리터럴로 읽어 낼 수 있지만
일반 변수를 그대로 사용했을 경우에는 그 값을 컴파일 타임에 읽지 못한다
그러므로 그 안에 값에 뭐가 들었는지 알 수 없기에
아래와 같이 사용하면 에러가 발한다
type head1 = First<arr>; // error
제네릭의 <>에는 타입을 넣어줘야 하는데 arr의 값이 뭐가 들었는지 컴파일 타임에는 모르기 때문이다. 그러므로 사용한 것이 typeof 키워드인데
type head1 = First<typeof arr>;
이걸 사용하면 컴파일 타임에 해당 변수의 타입을 읽어 올 수 있지만
이 역시 우리가 원했던 리터럴 ['a', 'b', 'c'] 가 아닌 string[] 형태로만 읽어와진다
이때 as const를 통해 상수화를 시켜두면
컴파일 타임에도 그 값을 읽어와서 리터럴 타입으로 사용할 수 있는 것이다
let arr = ['a', 'b', 'c'] as const;
// typeof arr은 ['a', 'b', 'c'] 리터럴 타입
type head1 = First<typeof arr>;
일반적으로 배열 또는 객체를 선언하면 TypeScript는 그 내용을 변경 가능하다고 가정하고 그에 맞는 타입을 추론한다
따라서, 배열이나 객체의 타입은 그 요소의 타입을 보다 덜 정확하게 나타내게 된다
하지만 as const를 사용하면 TypeScript는 해당 배열이나 객체의 내용이 변경되지 않을 것이라고
가정하고, 그에 따라 더 정확한 타입을 추론할 수 있는 것이다
즉, as const는 TypeScript에게 "이 변수는 변경되지 않습니다. 그래서 이 변수의 값은 항상 이것일 것입니다."라고 알려주는 역할을 한다. 그래서 as const가 적용된 변수의 값을 컴파일 타임에서 알 수 있게 되고 ,
배열이나 객체의 요소 각각에 대해 리터럴 타입을 추론하는 것을 가능하게 한다
따라서 as const를 사용하면 컴파일 타임에도 해당 배열이나 객체의 정확한 내용(값과 타입 모두)을 알 수 있다
이로 인해 리터럴 타입을 제네릭에 사용할 수 있게 되는 것이다
또한 , typeof를 제네릭에 사용할 때 만약 해당 타입이 readonly라면
반드시 제네릭에도 readonly를 작성해 줘야 한다
type First<T extends any[]> = T[0];
type arr1 = ['a', 'b', 'c'];
type head1 = First<arr1>; // expected to be 'a'
type First<T extends **readonly** any[]> = T[0];
const arr = ['a', 'b', 'c'] as const;
type head1 = First<typeof arr>; // expected to be 'a'
일반 변수에다가 as const를 사용함으로써 리터럴 타입 + 상수화로 인해 readonly가 된다 그러므로 이걸 제네릭에 넣어줄 대는 typeof 와 함께 넣어주는데 이때 , 제네릭에서도 반드시 readonly로 받아줘야 한다삼항 연산자를 사용한 경우에는 const assertion을 적용할 수 없다
let boolean = true;
let a = (boolean ? 'yes' : 'no') as const
// 'const' 어설션은 열거형 멤버나 문자열, 숫자, 부울, 배열 또는 개체 리터럴에 대한 참조에만 적용할 수 있습니다.ts(1355)
이러한 경우에는 삼항 연산자의 두 선택문 모두에 as const를 사용하여 타입을 좁힐 수 있다
let boolean = true;
let a = boolean ? 'yes' as const : 'no' as const; // let a: "yes" | "no"
참고 :