아이템 6 편집기를 사용하여 타입 시스템 탐색하기 타입스크립트는 언어서비스 (자동완성, 명세 검사, 검색, 리팩터링) 을 제공한다.
아이템 7. 타입이 값들의 집합이라고 생각하기 타입을 값의 집합으로 생각하면 이해하기 편하다. 타입은 엄격한 상속 관계가 아니라 겹쳐지는 집합으로 표현된다. 객체의 추가 속성이 타입 선언에 언급되지 않더라도 그 타입에 속할 수 있다. A 는 B를 상속 = A는 B에 할당가능 = A는 B의 서브타입 = A는 B의 부분집합
아이템 8 타입 공간과 값 공간의 심벌 구분하기 타입스크립트의 심벌은 타입 공간이나 값 공간 중 한 곳에 존재한다. 심벌은 이름이 같더라도 속하는 공간에 따라 다른 것을 나타낼 수 있다. interface Cylinder {
radius: number;
height: number;
}
const Cylinder = ( radius: number, height: number ) => ( { radius, height } ) ;
위 타입 Cylinder와 값 Cylinder는 동시에 존재 할 수 있다. 따라서 문맥에 따라 타입인지 값인지 파악할 수 있어야 한다. typeof, this 등 키워드들은 타입 공간과 값 공간에서 다른 목적으로 사용될 수 있다.
아이템 9 타입 단언보다는 타입 선언을 사용하기 interface Person {
name: string ;
}
const alice: Person = { name: "Alice" } ;
const bob = { name: "Bob" } as Person;
const alice: Person = {
name: "Alice" ,
age: 20 ,
} ;
const bob = {
name: "Bob" ,
age: 20 ,
} as Person;
타입 단언은 강제로 타입을 지정했기 때문에 타입체커에서 오류를 무시하라고 하는 것과 같다. 타입 선언에서의 안전성 체크가 단언에서는 불가능함 const people = [ "alice" , "bob" , "kim" ] . map ( ( name) : Person => ( { name } ) ) ;
화살표 함수에서의 리턴값 타입 지정도 선언문으로 하는게 좋다. 타입 단언은 타입체커가 추론한 타입보다 사용자가 판단하는 타입이 더 정확할 때 의미가 있다.
아이템 10 객체 래퍼 타입 피하기 js의 기본 타입(string, number, boolean, null, undefined, symbol, bigint, object)는 객체를 제외하면 메서드를 가지지 않는다. 'abc'.charAt(1) 과 같은 메서드는 'string' 기본형의 메서드가 아니다. 자바스크립트는 기본형을 String 객체로 래핑하고, 메서드를 호출하고, 마지막에 래핑한 객체를 버린다. 메서드 내의 this는 string 기본형이 아닌 String 객체 레퍼이다. 기본형 string과 객체 래퍼 String은 항상 동일하게 작동하지 않는다 'hello' === 'hello'
new String ( 'hello' ) === new String ( 'hello' )
보통은 래퍼 객체를 직접 생성할 필요가 없다. 기본형 타입은 객체 래퍼 타입에 할당할 수 있지만 반대는 불가능하다. 타입 스크립트는 기본형 타입을 객체 래퍼에 할당하는 선언을 허용하지만, 기본형 타입을 객체 래퍼에 할당하는 구문은 오해하기 쉽고, 굳이 그렇게 할 필요가 없으니 그냥 기본형 타입을 쓰자.
아이템 11 잉여 속성 체크의 한계 인지하기 interface Options {
title: string ;
darkMode? : boolean ;
}
const options: Options = { title: "hi" , darkmode: false } ;
타입스크립트는 해당 타입의 속성이 있는지, 그리고 그 외의 속성은 없는지 확인한다. 위의 darkmode 의 경우 darkMode 속성이 옵셔널이기 때문에 options 변수는 구조적 타입 관점에서 오류가 발생하지 않아야 하지만, 잉여 속성 체크 과정에서 오류를 발생시킨다. 잉여 속성 체크를 활용하면 기본적으로 타입 시스템의 구조적 본질을 해치지 않으면서도 객체 리터럴에 알 수 없는 속성을 허용하지 않음으로써 문제를 예방할 수 있다. const obj = { title: "hi" , darkmode: false } ;
const options: Options = obj;
잉여 속성 체크는 객체리터럴에만 적용된다. 위의 코드에선 obj는 임시 변수이기 때문에 잉여 속성 체크가 활성화 되지 않는다. 조건에 따라 동작하지 않는 경우가 있다는 한계가 있음.
아이템 12 함수 표현식에 타입 적용하기
function add ( a: number , b: number ) {
return a + b;
}
function minus ( a: number , b: number ) {
return a - b;
}
function mul ( a: number , b: number ) {
return a * b;
}
function div ( a: number , b: number ) {
return a / b;
}
type BinaryFn = ( a: number , b: number ) => number ;
const add2: BinaryFn = ( a, b) => a + b;
const minus2: BinaryFn = ( a, b) => a - b;
const mul2: BinaryFn = ( a, b) => a * b;
const div2: BinaryFn = ( a, b) => a / b;
타입스크립트에서는 함수 선언식 보다 함수 표현식을 사용하는게 좋다. 함수의 매개변수부터 반환값까지 전체를 함수 타입으로 선언하여 함수 표현식에 재사용할 수 있다는 장점이 있기 때문 라이브러리에서는 공통 함수 시그니처를 타입으로 제공하기도 한다. (ex. MouseEventHandler)
아이템 13 타입과 인터페이스의 차이점 알기 대부분의 경우 타입을 써도 되고 인터페이스를 써도 된다. 대부분 동일하다. type Input = { value: string } ;
type Output = { value: string } ;
interface VariableMap {
[ name: string ] : Input | Output;
}
type NamedVariable = ( Input | Output) & { name: string } ;
차이점으로 인터페이스는 유니온 타입 같은 복잡한 타입을 확장하지 못한다. 유니온 타입은 있지만, 유니온 인터페이스라는 개념은 없다. 일반적으로 type이 interface보다 쓰임새가 많다. 유니온이 될 수도 있고, 매핑된 타입 ㄸ/ㅗ는 조건부 타입 같은 고급 기능에 활용되기도 한다. type TPair = [ number , number ] ;
interface IPair {
0 : number ;
1 : number ;
length: 2 ;
}
튜플과 같은 배열 타입도 type을 사용하는 것이 더 간결하다. 인터페이스도 비슷하게 구현할 수는 있지만, concat과 같은 메서드들을 사용할 수 없다.
interface Istate {
name: string ;
age: number ;
}
interface Istate {
height: number ;
}
const kim: Istate = {
name: "kim" ,
age: 20 ,
height: 180 ,
} ;
선언 병합이라고 하는데 인터페이스에서만 사용가능하다. 타입 선언에서 사용자가 채워야 하는 빈틈이 있을 수 있는데, 이 경우 유용하다. 타입스크립트는 여러 버전의 자바스크립트 표준 라이브러리에서 여러 타입을 모아 병합한다. Array 인터페이스는 lib.es5.d.ts에 정의되어 있지만, tsconfig.json lib 목록에 es2015를 추가한다면 타입스크립트는 lib.es2015.d.ts에 선언된 인터페이스들을 병합한다. 타입은 기존 타입에 추가적인 보강이 없는 경우에만 사용하면 된다. ps. 프로젝트 내부적으로 사용되는 타입에 선언 병합이 발생하는 경우는 잘못된 설계이다.
결론 복잡한 타입이라면 type 사용 간단한 객체 타입이라면 일관성과 보강의 관점에서 고려하자
아이템 14 타입 연산과 제너릭 사용으로 반복 줄이기 DRY 원칙 - 같은 코드를 반복하지마라 - 타입에도 적용해야 한다. 타입에 이름을 붙여서 재사용하기 함수 시그니처를 명명된 타입으로 분리해서 재사용 인터페이스의 부분집합이 공유된다면 공통 필드만 골라서 기반 클래스로 분리 이미 존재하는 타입을 확장하는 경우에 인터섹션(&) 연산자 사용
Pick code interface State {
userId: string ;
pageTitle: string ;
recentFiles: string [ ] ;
pageContents: string ;
}
interface TopNavState {
userId: string ;
pageTitle: string ;
recentFiles: string [ ] ;
}
type TopNavState2 = {
userId: State[ "userId" ] ;
pageTitle: State[ "pageTitle" ] ;
recentFiles: State[ "recentFiles" ] ;
} ;
type TopNavState3 = {
[ k in "userId" | "pageTitle" | "recentFiles" ] : State[ k] ;
} ;
type TopNavState4 = Pick< State, "userId" | "pageTitle" | "recentFiles" > ;
Pick은 제너럴 타입이다. 함수를 호출하는 것과 비슷함. type와 key 두가지 타입을 받아서 결과 타입을 반환한다. keyof 타입을 받아서 속성 타입의 유니온을 반환한다. code type Options = {
width: number ;
height: number ;
color: string ;
} ;
type OptionKeys = keyof Options;
type OptionsUpdate = {
[ k in keyof Options] ? : Options[ k] ;
} ;
typeof 값의 형태에 해당하는 타입을 정의하고 싶을 때 사용한다. const INIT_OPTIONS = {
width: 640 ,
height: 480 ,
color: "white" ,
} ;
type Options = typeof INIT_OPTIONS ;
제너릭 타입은 타입을 위한 함수와 같다. 제너릭 타입에서 매개변수를 제한하기 위한 방법으로 extends 사용
아이템 15 동적 데이터에 인덱스 시그니처 사용하기 type Rocket = { [ key: string ] : string } ;
const rocket: Rocket = {
name: "Falcon 9" ,
variant: "v1.0" ,
} ;
위의 인덱스 시그니처 단점 잘못된 키를 포함해 모든 키를 허용한다. 특정 키가 필요하지 않다.( {} 도 허용 ) 키마다 다른 타입을 가질 수 없다. 언어 시스템이 도움이 되지 못한다. ( 자동완성 x ) 동적 데이터를 사용하고 싶을 때 쓰자. ( ex. CSV)
아이템 16 number 인덱스 시그니처보다는 Array, 튜플, ArrayLike 사용하기 key의 타입을 number로 지정하더라도, 런타임에선 string으로 인식된다. number 타입의 키가 필요하다면 배열을 쓰자. 배열의 push, concat같은 다른 속성이 없어야 한다면 ArrayLike
아이템 17 변경 관련된 오류 방지를 위해 readonly 사용하기 function printTriangles ( n: number ) {
const nums = [ ] ;
for ( let i = 0 ; i < n; i++ ) {
nums. push ( i) ;
console . log ( arraySum ( nums) ) ;
}
}
function arraySum ( arr: readonly number [ ] ) {
let sum = 0 ,
num;
while ( ( num = arr. pop ( ) ) !== undefined ) {
sum += num;
}
return sum;
}
printTriangles ( 5 ) ;
readonly number[] 특징 배열의 요소를 읽을 수 있지만, 쓸 수는 없다. length를 읽을 수 있지만, 바꿀 수 없다. 배열을 변경하는 pop을 비롯한 다른 메서드를 호출할 수 없다. readonly number[] 보다 number[]의 기능이 많기 때문에 number[]는 서브타입이 된다. 따라서 변경 가능한 배열을 readonly 배열에 할당할 수 있지만, 그 반대는 불가능하다. readonly를 선언하면 다음과 같이 동작한다 타입스크립트는 매개변수가 함수 내에서 변경이 일어나는지 체크함 호출하는 쪽에서는 함수가 매개변수를 변경하지 않는다는 보장을 받게 된다. 호출하는 쪽에서 함수에 readonly 배열을 매개변수로 넣을 수 있다.
아이템 18 매핑된 타입을 사용하여 값을 동기화하기
Loading Comments...