타입스크립트를 사용하는 경우 의존성 관리 측면에서 다음 세 가지의 사항을 추가로 고려해야한다.
세가지 버전 중 하나라도 맞지 않으면 오류가 발생할 수 있다.
라이브러리와 타입 정보의 버전이 별도로 관리되는 방식의 문제점
일반적으로 ts라이브러리들은 자체적으로 타입 선언을 포함(번들링)하게 된다. → index.d.ts
파일을 가리키게함 그러나 이러한 번들링 방식은 부수적인 4가지 문제점을 가짐
공식적인 권장사항은 라이브러리가 TS로 작성된 경우만 타입선언을 라이브러리에 포함시키는것!
JS로 작성된 라이브러리는 타입 선언을
DefinitelyTyped
에 공개하여 커뮤니티에서 관리하고 유지보수하도록 맡기는 것이 좋다!
interface SecretName {
first: string;
last: string;
}
interface SecretSanta {
name: SecretName;
gift: string;
}
export function getGift(name: SecretName, gift: string): SecretSanta {
// ...
}
이렇게 특정 타입을 익스포트하지 않고 숨겨도 아래와 같이 타입은 노출된다.
type MySanta = ReturnType<typeof getGift>; // SecretSanta
type MyName = Parameters<typeof getGift>[0]; // SecretName
→ 따라서 라이브러리 사용자를 위해 굳이 타입을 숨기려하지말고 명시적으로 익스포트 하자!
인라인 스타일의 주석보다 JSDoc을 사용하면 툴팁으로 아래와 같이 표시해준다.
→ 따라서 인라인 스타일 대신 JSDoc 스타일의 주석을 달자
타입스크립트 관점에서는 TSDoc이라고 하는데, JSDoc과 달리 @params, @returns와 같은 타입 정보를 명시하는 규칙대신 타입 정보가 코드에 있기 때문에 TSDoc에서는 타입 정보를 명시하면 안된다. 또한 TSDoc과 JSDoc은 마크다운 형식으로 문서를 서식할 수 있다.(강조, 기울임 등등..)
이 장은 주로 this에 관해 다루는데 JS의 this
동작 방식을 알고 있다면 쉽게 이해할 수 있다.
이 장의 대부분의 내용은 this
의 동작방식에 대해 설명하고 있어서 TypeScript
와 관련된 부분은 아래와 같다.
function addKeyListener (
el: HTMLElement,
fn: (this: HTMLElement, e:KeyboardEvent) => void
) {
el.addEventListener('keydown', e => {
fn(e); // this 바인딩 체크
})
}
타입스크립트에서 콜백 함수의 첫 번째 매개변수에 있는
this
는 특별하게 처리된다.call
로this
바인딩 없이 매개변수를 2개 넘기면 1개의 인수만을 원한다고 하며,this
바인딩을 체크해주기 때문에 실수를 방지해준다.
→ 콜백 함수에서 this를 사용해야 한다면, 타입 정보를 명시하자!
오버로딩으로 타입에 따른 함수들을 다 정의하기보다, 아래와 같이 조건부 타입을 이용하면 유니온 타입 또한 조건부 타입의 유니온으로 분리되어 잘 작동한다.
function double<T extends string | number>(x: T): T extends string ? string : number;
오버로딩 타입보다 조건부 타입을 사용하여 간단한게 작성하자.
미러타입: 라이브러리에서 필요한 선언부만 추출하여 작성중인 라이브러리에 넣는 것
function parseCSV(contents: string | Buffer): { [column: string]: string }[] {
if (typeof contents === 'object') {
// 버퍼인경우
return parseCSV(contents.toString('utf-8'));
}
}
위와 같은 코드가 있을 때,
Nodejs
를 사용자를 위한Buffer
타입이 존재하지만 이는 타입과 무관한 자바스크립트 개발자나 Nodejs와 무관한 타입스크립트 웹 개발자들에게는 의미가 없다.
→ 따라서 이러한 경우 타입을 devDependencies
로 추가하기보다, 각자가 필요한 모듈만 사용할 수 있도록 구조적 타이핑을 적용하면 좋다. 그러나 타입의 대부분을 추출해야하는 경우는 명시적으로 @types
의존성을 추가하는게 낫다.
이 아이템에선 테스트코드의 타입을 체크하기 위해 반환 타입을 체크하는 과정에서 발생하는 여러 문제들을 다루고 있다.
최종적으로 이 장에서 말하고자 하는 바는 DefinitelyTyped
의 타입 선언을 위한 도구로 dtslint
를 소개하며 이를 사용하는 것을 추천한다.
const beatles = ['john', 'paul', 'george', 'ringo'];
map(beatles, function(
name, // $ExpectType string
i, // $ExpectType number
array // $ExpectType string[]
) {
this // $ExpectType string[]
return name.length;
}) // $ExpectType number[]
dtslint
를 사용한 테스트코드
dtslint
는 할당 가능성을 체크하는 대신 각 심벌의 타입을 추출하여 글자 자체가 같은지 비교한다. 그러나 이러한 방식에도 단점이 있는데, number|string
과 string|number
는 같은 타입이지만 글자 자체로 보면 다르기 때문에 다른 타입으로 인식된다.
자바슼릡트가 초기에는 결함이 많고 개선해야하는 부분이 많은 언어여서 타입스크립트에 다음과 같은 신규기능들이 있었다.
그러나 각각 자바스크립트와 잘 호환되지 않고, 타입스크립트의 역할을 명확하게하는 측면에서 이것들을 사용하지 않는 것이 좋다!
const obj = {
one: 'uno',
two: 'dos',
three: 'tres',
}
for (const k in obj) {
const v = obj[k] // 에러발생
}
// 다음과 같이 사용
let k: keyof typeof obj;
for (k in obj) {
const v = obj[k]
}
k와 obj 객체의 키 타입이 서로 다르게 추론되어 오류가 발생한다. 이 경우
keyof
을 사용하여 해결한다.
interface ABC {
a: string;
b: string;
c: number;
}
function foo(abc: ABC) {
for (const k in abc) {
const v = abc[k]; // 에러 인덱스 시그니처가 없고 암시적 'any'가 되므로
}
}
const x = { a: 'a', b: 'b', c: 2, d: new Date() };
foo(x); // 정상
위 경우 인터페이스 ABC에는 구조적타이핑에 따라 a, b, c외에 다른 속성이 존재할 수 있는 타입이 foo함수 내부로 들어올 수 있기 때문에 타입스크립트는 ABC 타입의 키를 string 타입으로 선택하게 된다.
여기서 맨 위에서 해결한 keyof을 사용하개 되면 인터페이스의 타입이 한정되고, 새로운 타입이 들어왔을 때 문제가 발생한다. → 이 경우 Object.entries
를 사용하면 좋다.
또한 프로토타입의 오염을 신경쓰면서 속성을 추가하고, for-in 문은 프로토타입 체인상에 존재하는 프로퍼티들을 보여주기 때문에 이 점을 유의하자!
요약
keyof T
와 for-in
루프를 사용하자. 또한 함수의 매개변수로 쓰이는 객체에는 추가적인 키가 존재할 수 있다는 점을 명시!Object.entries
를 사용하는 것이다.