Blog

이펙티브 타입스크립트(6)

이펙티브 타입스크립트(6)

6-1. devDependencies 에 typescript 와 @types 추가하기

npm 패키지 의존성 영역
dependencies
프로젝트를 실행하는데 필수적인 라이브러리들을 포함
프로젝트를 npm 에 공개하여 다른 사용자가 이 프로젝트를 설치하면 dependencies 에 있는 내용들도 설치됨
이 현상을 전이(transitive) 의존성 이라고 함
devDependencies
개발하고 테스트하는데 사용되지만, 런타임 화경에서는 필요없는 라이브러리들을 포함
프로젝트를 npm 에 공개하여 다른 사용자가 이 프로젝트를 설치해도 devDependencies 에 있는 라이브러리들은 설치하지 않음
peerDependencies
런타임에 필요하긴 하지만, 의존성을 직접 관리하지 않는 라이브러리들을 포함
실제 프로젝트에서 필요하 라이브러리 버전을 선택할 수 있도록 제안
타입 정보는 런타임 환경에 존재하지 않으므로 대부분 devDependencies 에 존재해야함
타입스크립트 프로젝트에서 공통적으로 고려해야 할 의존성
1.
타입스크립트 자체 의존성
타입스크립트를 시스템(글로벌) 레벨로 설치할 수 있지만, 추천하지 않음
같은 프로젝트를 작업하는 사람들의 타입스크립트 버전이 동일하다는것을 보장할 수 없음
프로젝트를 셋업할 때 별도의 단계가 추가됨
2.
타입 의존성(@types)
사용하려는 라이브러리에 타입 선언이 포함되어 있지 않아도, DefinitelyTyped 에서 타입 정보를 얻을 수 있다

요약

타입스크립트를 시스템 레벨로 설치하면 안된다
@types 의존성은 devDependencies 에 포함시켜야 한다

6-2. 타입 선언과 관련된 세 가지 버전 이해하기

타입스크리브를 사용하면 버전에 대해 3가지 고민해야 한다
1.
라이브러리의 버전
2.
타입 선언(@types) 의 버전
3.
타입스크립트의 버전
실제 라이브러리와 타입 정보의 버전이 별도 관리되는 방식의 4가지 문제점
1.
라이브러리르 업데이트했지만 실수로 타입 선언은 업데이트 하지 않음
하위 호환성이 깨지는 변경이 있었다면, 코드가 타입 체커를 통과하더라도 런타임에 오류가 발생할 수 있음
일반적으로 타입 선언도 업데이트해서 문제를 해결할 수 있찌만, 아직 버전이 없다면 보강(augmentation) 기법을 활용하거나, 타입 선언의 업데이트를 직접 작성해서 커뮤니티에 기여한다
2.
라이브러리보다 타입 선언의 버전이 최신인 경우
라이브러리와 타입 선언의 버전이 맞도록 라이브러리 버전을 올리거나 타입 선언의 버전을 내려서 해결한다
3.
프로젝트에서 사용하는 타입스크립트 버전보다 라이브러리에서 필요로 하는 타입스크립트 버전이 최신인 경우
프로젝트의 타입스크립트 버전을 올리거나, 라이브러리 타입 선언의 버전을 원래대로 내리거나, declare module 선언으로라이브러리의 타입 정보를 없애서 해결한다
4.
@types 의존성이 중복될 경우
라이브러리의 의존성이 중복된 경우 서로 버전이 호환되게 해서 해결한다
그러나 전이 의존성을 가지도록 만드는 것은 종종 문제를 일으킨다
라이브러리에서 타입을 같이 번들링하여 타입 선언을 포함하는 경우, 버전 불일치 문제를 해결할 수 있다
이로 인해 발생할 수 있는 문제
1.
번들된 타입 선언에 보강 기법으로 해결할 수 없는 오류가 있는 경우, 또는 공개 시점에는 잘 동작했지만 타입스크립트 버전이 올라가면서 오류가 발생하는 경우 문제가 된다
2.
프로젝트 내의 타입 선언이 다른 라이브러리의 타입 선언에 의존한다면 문제가 생긴다
3.
프로젝트의 과거 버전에 있는 타입 선언에 문제가 있는 경우에는 과거 버전으로 돌아가서 패치 업데이트를 해야 한다
4.
타입 선언의 패치 업데이트를 자주 하기 어렵다
공식적인 권장사항은 라이브러리가 타입스크립트로 작성된 경우만 타입 선언을 라이브러리에 포함하는것이다
자바스크립트로 작성된 라이브러리는 타입 선언을 DefinitelyTyped 에 공개하여 커뮤니티에서 관리하고 유지보수하도록 맡기는것이 좋다

요약

@types 의존성과 관련된 3가지 버전이 있다
라이브러리를 업데이트 하는 경우 @types 도 같이 업데이트 해야 한다
타입 선언을 라이브러리에 포함하는 것과 DefinitelyTyped 에 공개하는 것 사이의 장단점을 이해해야 한다

6-3. 공개 API 에 등장하는 모든 타입을 익스포트하기

공개 API 매개변수에 놓이는 순간 타입은 노출되기 때문에 굳이 숨기려하지 말고 라이브러리 사용자를 위해 명시적으로 export 하는 것이 좋다

6-4. API 주석에 TSDoc 사용하기

export 된 함수, 클래스, 타입에 주석을 달 때는 JSDoc / TSDoc 형태를 사용해야 한다
@param, @returns 구문과 문서 서식을 위해 마크다운을 사용할 수 있다
주석에 타입 정보를 포함하면 안된다

6-5. 콜백에서 this 에 대한 타입 제공하기

let, const 로 선언된 변수가 렉시컬 스코프(lexical scope) 인 반면, this 는 다이나믹 스코프(dynamic scope) 이다
콜백 함수에서 this 값을 사용해야 한다면 this 는 API 의 일부가 되는 것이기 때문에 반드시 타입 선언에 포함해야 한다

6-6. 오버로딩 타입보다는 조건부 타입을 사용하기

오버로딩 타입이 작성하기는 쉽지만, 조건부 타입은 개별 타입의 유니온으로 일반화하기 때문에 타입이 더 정확해진다
예시 코드
오버로딩
function double(x: number|string): number|string; function double(x: any) { return x + x };
TypeScript
제네릭
function double<T extends number|string>(x: T): T; function double(x: any) { return x + x };
TypeScript
오버로딩(2)
function double(x: number): number; function double(x: string): string; function double(x: any) { return x + x; }
TypeScript
조건부 타입
function double<T extends number | string>( x: T ): T extends string ? string : number; function double(x: any) { return x + x; }
TypeScript
타입 오버로딩이 필요한 경우에 가끔 조건부 타입이 필요한 상황이 발생한다

요약

오버로딩 타입보다 조건부 타입을 사용하는 것이 좋다

6-7. 의존성 분리를 위해 미러 타입을 사용하기

만약 작성중인 라이브러리가 의존하는 라이브러리의 구현과 무관하게 타입에만 의존한다면, 필요한 선언부만 추출하여 작성중인 라이브러리에 넣는 것을 고려해보는것도 좋다
예시 코드
interface CsvBuffer { tostring(encoding: string): string; } function parseCSV(contents: string | CsvBuffer): {[column: string]: string}[] { // ... }
TypeScript
다른 라이브러리의 타입이 아닌 구현에 의존하는 경우에도 동일한 기법을 적용할 수 있고, 타입 의존성을 피할 수 있다
미러링 기법은 유닛 테스트와 사용 시스템 간의 으존성을 분리하는 데도 유용하다

요약

필수가 아닌 의존성을 분리할 때는 구조적 타이핑을 사용하면 된다
공개한 라이브러리를 사용하는 자바스크립트 사용자가 @types 의존성을 가지지 않게 해야 한다

6-8. 테스팅 타입의 함정에 주의하기

타입 선언에 대해 테스트를 할 때 dtslint 또는 타입 시스템 외부에서 타입을 검사하는 유사한 도구를 사용하는 것이 안전하고 간단하다
타입 선언 파일을 테스팅할 때는 단순히 함수를 실행만 하는 방식 보다는 실제로 반환 타입까지 체크하는게 훨신 좋은 테스트 코드이다

요약

타입을 테스트할 때는 특히 함수 타입의 동일성 (equality)과 할당 가능성 (assignability) 의 차이점을 알고 있어야 한다
콜백이 있는 함수를 테스트할 때 콜백 매개변수의 추론된 타입을 체크해야 한다
타입 관련된 테스트에서 any 를 주의해야 한다
더 엄격한 테스트를 위해 dtslint 같은 도구를 사용하는것이 좋다