Effective Typescript에 대한 스터디 진행 내용을 요약한 것입니다.
WILIM 프로젝트를 진행하느라 스터디를 긴 시간 못했다.
마저 진행해보자!
우리가 알고 있는 Javascript 기본형들에 대한 7가지 타입은 다음과 같다.
string
number
boolean
null
undefined
symbol
bigInt
기본형들은 불변이며, 내장 메서드를 가질 수 없다는 특징을 가진다. 말 그대로 언어 내 가장 기본적인 단위인 것이다.
여기서 JS를 자주 사용해본 사람이라면 뭔가 이상함을 알아챌 수 있을 것이다.
string
을 배열처럼 여기고 사용한 메서드들은 뭐지??
맞다...node
를 통해 다음 명령어를 시행하면 정상 작동하는 것을 볼 수 있다.
console.log('primitive'.charAt(3));
// 'm' 출력
이는 string
타입에는 메서드가 없지만 JS 내에 String
이라는 객체 타입이 존재하기 때문이다. JS는 이와 같이 기본형과 객체타입을 자유롭게 변환한다. 이 덕분에 우리가 기본 문자열에도 메서드를 사용할 수 있는 것이다.
string
에 char~와 같은 메서드를 사용할 때 JS는 이를 String
객체로 래핑하고 메서드를 실행한 뒤에 감싼 String
객체를 버리는 방식으로 연산을 수행한다.
그래서 아래와 같은 일들이 일어난다.
'hello' === new String('hello'); // false
new String('hello') === new String('hello'); // false
혹은 아래와 같은 일이 발생한다.
x = 'hello' // 변수 x 내에 'hello'를 담고
x.language = 'english' // x를 객체로 래핑하여 language라는 값을 넣어줄 수는 있다.
x.language // undefined -> 객체로 래핑하여 값을 넣고 래핑된 객체를 다시 버렸으므로 당연히 undefined가 나온다.
이때 TS에서는 다음과 같은 규칙이 존재한다.
string은 String자리에 들어갈 수 있지만(String의 인스턴스로 간주)
String은 string 자리에 들어갈 수 없다!
이에 대한 예제는 다음과 같다.
function isGreeting(phrase: String) {
return ['hello', 'good day'].includes(phrase) // Argument of type 'String' is not assignable to parameter of type 'string'.
대신 다음은 성립한다.
const a: String | string = new String();
// a 는 String의 인스턴스이므로 String과 string이 모두 가능하다.
예외적으로 bigInt
와 Symbol
은 new 생성자 사용이 불가하기 때문에 어디에서나 똑같이 인식이 된다.
앞에서 배운 구조적 타이핑을 이해하고 있다면 다음 코드가 어렵지 않을 것이다.
interface Room {
numDoors: number;
ceilingHeightFt: number;
}
const obj = {
numDoors: 1,
ceilingHeightFt: 10,
elephant: 'present',
}
const r: Room = obj;
//const obj: {
// numDoors: number;
// ceilingHeightFt: number;
// elephant: string;
//}
위 결과 obj의 타입이 잘 확인되는 것을 알 수 있다. r은 Room의 타입을 가지고 있고, obj가 해당 타입의 속성을 모두 만족하고 있으므로 구조적 타이핑이 적용되어 있는 모습이다.
그러나 다음 코드는 그렇지 못하다.
numDoors: number;
ceilingHeightFt: number;
}
const obj: Room = {
numDoors: 1,
ceilingHeightFt: 10,
elephant: 'present',
}
// Object literal may only specify known properties, and 'elephant' does not exist in type 'Room'.
const r: Room = obj;
위의 코드처럼 obj에 Room이라는 타입을 지정하게 되면 Room의 속성에 맞지 않는 elephant
라는 속성이 충돌을 일으키게 되는 것이다.
다만 이러한 관용(?)은 다음과 같은 극단적인 경우를 통과시키기도 한다...
interface Room {
numDoors: number;
ceilingHeightFt: number;
}
interface Options {
title: string;
darkMode?: boolean;
}
const o1: Options = document;
const o2: Options = new HTMLAnchorElement();
interface인 Options는 title
만 존재한다면 만사 OK이기 때문에 document
나 HTMLAnchorElement
와 같은 title
이 존재하는 어떤 객체도 받아들일 수 있게 되는 것이다...
또 재밌는 점은 다음의 코드이다.
interface Room {
numDoors: number;
ceilingHeightFt: number;
}
interface Options {
title: string;
darkMode?: boolean;
}
const inter1: Options = { darkmode: true, title: 'hello' };
// Object literal may only specify known properties, but 'darkmode' does not exist in type 'Options'. Did you mean to write 'darkMode'?
const inter2 = { darkmode: true, title : 'hello' };
const a: Options = inter2;
위의 타입 선언과 같은 경우 darkmode
를 오타로 인식하고 친절하게 darkMode
를 사용할 것을 말해주고 있다.
그러나 아래의 경우 그런 것 없이 darkmode
를 다른 속성이라고 생각하고 받아들이고 있다...여기에서도 타입 단언보다 타입 선언이 사용되어야 하는 이유를 볼 수 있는 것 같다.
마지막으로 아래 예제를 살펴보자.
interface LineChartOptions {
logscale?: boolean;
invertedYAxis?: boolean;
areaChart?: boolean;
}
const opts = { logScale: true };
const a: LineChartOptions = opts;
// Type '{ logScale: boolean; }' has no properties in common with type 'LineChartOptions'.
여기에서는 LineChartOptions
라는 인터페이스가 모두 선택적 속성을 제공하고 있음에도 a
는 오류를 발생시킨다. 이는 opts
에 LineChartOptions
와 공통으로 가지는 속성이 단 하나도 없어서 발생하는 것으로, 다음과 같이 고칠 수 있다.
interface LineChartOptions {
logscale?: boolean;
invertedYAxis?: boolean;
areaChart?: boolean;
}
const opts = { logScale: true, logscale: true };
const a: LineChartOptions = opts;
즉 logScale
을 오타 수정이 아닌 완전히 다른 속성으로 간주하는 것이다.
이러한 경우 에러를 없애려면 선택적 속성만 가진 인터페이스라도 겹치는 속성이 하나라도 존재하도록 선언해야한다.
interface LineChartOptions {
logscale?: boolean;
invertedYAxis?: boolean;
areaChart?: boolean;
}
const opts = { logscale: true, title: true };
const a: LineChartOptions = opts;
위와 같은 코드는 성립한다는 이야기이다. 또한, opts
객체 내에 아무것도 없는 경우도 에러를 발생시키지 않는다.
이처럼 타입 단언과 비슷한 방식으로 타입을 선언하게 되면 확장에 유연한 객체가 생성되지만, 그만큼 엄격하지 못한 객체가 생성되는 것이라고 생각할 수 있다.
상황에 맞게 선언과 단언을 선택하여 사용하도록 하자..!