interface Album {
artist: string
title: string
releaseDate: string // YYYY-MM-DD
recordingType: string // E.g., "live" or "studio"
}
위와 같이 무작정 string
으로 쓰는 건 피해야 한다.
/** 어느 장소에서 녹음되었는지? */
type RecordingType = 'studio' | 'live'
interface Album {
artist: string
title: string
releaseDate: Date
recordingType: RecordingType
}
위와 같이 유니온 타입을 정의해서 사용하고, 날짜 같은 경우 Date 객체로 사용해서 날짜 형식만 입력할 수 있도록하는 것이 낫다. 이렇게 하면 세가지 장점이 있다.
타입을 명시적으로 정의하고 해당 타입의 의미를 설명하는 주석을 붙여넣을 수 있다.
-> 몰랐는데 유용한 방식이라고 생각된다.
function pluck<T, K extends keyof T>(record: T[], key: K): T[K][] {
return record.map(r => r[key])
}
코드를 더 정밀하게 만들려던 시도가 과하면 그로 인해 코드가 오히려 더 부정확해질 수 있다. 타입에 의존하면 부정확함으로 인해 발생하는 문제가 더 커질 수 있다.
type Expression1 = any
type Expression2 = number | string | any[]
type Expression4 = number | string | CallExpression
type CallExpression = MathCall | CaseCall | RGBCall
interface MathCall {
0: '+' | '-' | '/' | '*' | '>' | '<'
1: Expression4
2: Expression4
length: 3
}
interface CaseCall {
0: 'case'
1: Expression4
2: Expression4
3: Expression4
length: 4 | 6 | 8 | 10 | 12 | 14 | 16 // etc.
}
interface RGBCall {
0: 'rgb'
1: Expression4
2: Expression4
3: Expression4
length: 4
}
const tests: Expression4[] = [
10,
'red',
true,
// ~~~ Type 'true' is not assignable to type 'Expression4'
['+', 10, 5],
['case', ['>', 20, 10], 'red', 'blue', 'green'],
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type '["case", [">", ...], ...]' is not assignable to type 'string'
['**', 2, 31],
// ~~~~~~~~~~~~ Type '["**", number, number]' is not assignable to type 'string
['rgb', 255, 128, 64],
['rgb', 255, 128, 64, 73],
// ~~~~~~~~~~~~~~~~~~~~~~~~ Type '["rgb", number, number, number, number]'
// is not assignable to type 'string'
]
const okExpressions: Expression4[] = [
['-', 12],
// ~~~~~~~~~ Type '["-", number]' is not assignable to type 'string'
['+', 1, 2, 3],
// ~~~~~~~~~~~~~~ Type '["+", number, ...]' is not assignable to type 'string'
['*', 2, 3, 4],
// ~~~~~~~~~~~~~~ Type '["*", number, ...]' is not assignable to type 'string'
]
타입을 정교하게 하려다가 발생하는 오류들이 이전보다 더 부정확해질 수 있다. 그리고 타입선언이 자동 완성을 방해해서 생산성을 떨어뜨릴 수도 있다.
타입 안정성에서 불쾌한 골짜기는 피해야 한다. 타입이 없는 것보다 잘못된 것이 더 나쁘다.
불쾌한 골짜기 : 어설프게 완벽을 추구하다가 오히려 역효과가 발생하는 것.
any
와 unknown
을 구별해서 사용해야 한다.export interface getLicense_repository_licenseInfo {
__typename: 'License'
/** Short identifier specified by <https://spdx.org/licenses> */
spdxId: string | null
/** The license full name specified by <https://spdx.org/licenses> */
name: string
}
export interface getLicense_repository {
__typename: 'Repository'
/** The description of the repository. */
description: string | null
/** The license associated with the repository */
licenseInfo: getLicense_repository_licenseInfo | null
}
export interface getLicense {
/** Lookup a given repository by the owner and repository name. */
repository: getLicense_repository | null
}
export interface getLicenseVariables {
owner: string
name: string
}
GraphQL에서는 특정 쿼리에 대해 타입스크립트 타입을 생성할 수 있다. GraphQL 쿼리를 타입스크립트 타입으로 변환해주는 Apollo와 같은 도구들이 있다. 위는 Apollo를 통해 자동 생성된 결과이다. 자동으로 생성된 타입 정보는 API를 정확히 사용할 수 있도록 돕는다.
interface Animal {
commonName: string;
genus: string;
species: string;
status: ConservationStatus;
climates: KoppenClimate[];
}
type ConservationStatus = 'EX' | 'EW' | 'CR' | 'EN' | 'VU' | 'NT' | 'LC';
type KoppenClimate = |
'Af' | 'Am' | 'As' | 'Aw' |
'BSh' | 'BSk' | 'BWh' | 'BWk' |
'Cfa' | 'Cfb' | 'Cfc' | 'Csa' | 'Csb' | 'Csc' | 'Cwa' | 'Cwb' | 'Cwc' |
'Dfa' | 'Dfb' | 'Dfc' | 'Dfd' |
'Dsa' | 'Dsb' | 'Dsc' | 'Dwa' | 'Dwb' | 'Dwc' | 'Dwd' |
'EF' | 'ET';
const snowLeopard: Animal = {
commonName: 'Snow Leopard',
genus: 'Panthera',
species: 'Uncia',
status: 'VU', // vulnerable
climates: ['ET', 'EF', 'Dfd'], // alpine or subalpin
타입, 속성, 변수에 이름을 붙일 때 명심해야할 세가지 규칙이 있다.
data, info, thing, item, object, entity와 같이 모호하고 의미 없는 이름은 피해야 한다.
-> 이 부분에서 많이 찔린다..!
interface Vector2D {
x: number
y: number
}
function calculateNorm(p: Vector2D) {
return Math.sqrt(p.x * p.x + p.y * p.y)
}
calculateNorm({ x: 3, y: 4 }) // OK, result is 5
const vec3D = { x: 3, y: 4, z: 1 }
calculateNorm(vec3D) // OK! result is also 5
interface Vector2D {
_brand: '2d'
x: number
y: number
}
function vec2D(x: number, y: number): Vector2D {
return { x, y, _brand: '2d' }
}
function calculateNorm(p: Vector2D) {
return Math.sqrt(p.x * p.x + p.y * p.y) // Same as before
}
calculateNorm(vec2D(3, 4)) // OK, returns 5
const vec3D = { x: 3, y: 4, z: 1 }
calculateNorm(vec3D)
// ~~~~~ Property '_brand' is missing in type...
위와 같이 _brand
를 붙이는 것을 상표(brand) 기법이라고하는데, 타입 시스템에서 동작하지만 런타임에 상표를 검사하는 것과 동일한 효과를 준다. (Nominal Typing)
interface Foo {
foo: string
}
interface Bar {
bar: string
}
declare function expressionReturningFoo(): Foo
function processBar(b: Bar) {
/* ... */
}
function f() {
const x = expressionReturningFoo()
processBar(x)
// ~ Argument of type 'Foo' is not assignable to
// parameter of type 'Bar'
}
function f1() {
const x: any = expressionReturningFoo() // Don't do this
processBar(x)
}
function f2() {
const x = expressionReturningFoo()
processBar(x as any) // Prefer this
}
위 상황에서 두 가지 해결책 중 x:any
보다 x as any
가 권장된다. 그 이유는 any
타입이 해당 함수의 매개변수에만 사용된 표현식이어서 다른 코드에 영향을 주지 않기 때문이다. f1()
는 변수 x
의 타입 자체가 any
로 바뀌어버린다.
interface Foo {
foo: string
}
interface Bar {
bar: string
}
declare function expressionReturningFoo(): Foo
function processBar(b: Bar) {
/* ... */
}
function f1() {
const x = expressionReturningFoo()
// @ts-ignore
processBar(x)
return x
}
위와 같이 @ts-ignore
를 사용하면 다음 줄의 오류가 무시된다. 하지만 근본적인 문제를 해결한 것은 아니기 때문에 해결책이라고는 할 수 없다. any
는 최소한의 범위에만 사용해야 타입 체크가 안되거나 하는 등의 부작용이 생기지 않는다.
any
의 사용 범위를 최소한으로 좁혀야 한다.any
인 경우 타입 안정성이 나빠진다. 따라서 any
타입을 반환하지 않도록 하자any
대신 @ts-ignore
를 사용하는 것이 좋다.function getLengthBad(array: any) {
// Don't do this!
return array.length
}
function getLength(array: any[]) {
return array.length
}
언뜻 보면 위 코드에서 any
나 any[]
가 큰 차이가 있는 것 같지는 않지만, 아래 함수가 세 가지 이유에서 더 나은 함수이다.
any
대신 number
로 추론된다.그리고 함수의 매개변수가 객체인데, 값을 알 수 없다면 {[key:string] :any}
처럼 선언하면 된다.
any
를 사용할 때는 정말로 모든 값이 허용되어야하는지 검토해야 한다.any
보다 더 정확하게 모델링할 수 있도록 any[]
또는 {[id:string] :any}
또는 () => any
처럼 구체적인 형태를 사용해야 한다.불필요한 예외 상황까지 고려하여 타입 정보를 힘들게 구성하기보다, 함수 내부에는 타입 단언을 사용하고 함수 외부로 드러나는 타입 정의를 정확히 명시하는게 낫다.
출처