|
| 1 | +## 💡 함수 표현식에 타입 적용하기 |
| 2 | + |
| 3 | +자바스크립트와 타입스크립트에서는 함수 '문장(statement)'과 함수 '표현식(expression)'을 다르게 인식합니다. |
| 4 | + |
| 5 | +``` |
| 6 | +function rollDice1(sides:number): nimber {} |
| 7 | +``` |
| 8 | + |
| 9 | +위 코드는 문장(statement)입니다. 반면 아래 코드들은 함수 표현식입니다. |
| 10 | + |
| 11 | +``` |
| 12 | +const rollDice2 = function(sides: number): number {}; |
| 13 | +const rollDice3 = (sides: number) : number => {}; |
| 14 | +``` |
| 15 | + |
| 16 | +타입스크립트에서는 함수 표현식을 사용하는 것이 좋습니다. 함수의 매개변수부터 반환값까지 전체를 함수 타입으로 선언하여 함수 표현식에 재사용할 수 있기 때문입니다. |
| 17 | + |
| 18 | +type DiceRollFn = (sides: number) => number; |
| 19 | +const rollDice: DiceRollFn = sides => {}; |
| 20 | + |
| 21 | +sides에 마우스를 올려 보면 이미 타입스크립트가 sides의 타입을 number로 인식하고 있다는 걸 알 수 있습니다. |
| 22 | + |
| 23 | +함수 타입의 선언은 불필요한 코드의 반복을 줄입니다. |
| 24 | + |
| 25 | +``` |
| 26 | +function add(a:number, b: number) {return a + b}; |
| 27 | +function sub(a:number, b: number) {return a - b}; |
| 28 | +function mul(a:number, b: number) {return a * b}; |
| 29 | +function div(a:number, b: number) {return a / b}; |
| 30 | +
|
| 31 | +``` |
| 32 | + |
| 33 | +위의 코드처럼 반복되는 함수 시그니처를 일일이 적어줄 필요 없이 하나의 함수 타입으로 통합할 수도 있습니다. |
| 34 | + |
| 35 | +``` |
| 36 | +type BinaryFn = (a:number, b: number) => number; |
| 37 | +const add: BinaryFn = (a,b) => a+b; |
| 38 | +const sub: BinaryFn = (a,b) => a-b; |
| 39 | +const mul: BinaryFn = (a,b) => a*b; |
| 40 | +const div: BinaryFn = (a,b) => a/b; |
| 41 | +``` |
| 42 | + |
| 43 | +이처럼 함수의 매개변수에 타입 선언을 하는 것보다 함수 표현식 전체 타입을 정의하는 것이 코드도 간결하고 안전합니다. 다른 함수의 시그니처와 동일한 타입을 가지는 새 함수를 작성하거나, 동일한 타입 시그니처를 가지는 여러 개의 함수를 작성할 때는 매개변수의 타입과 반환 타입을 반복해서 작성하지 말고 함수 전체의 타입 선언을 적용해야 합니다. |
| 44 | + |
| 45 | +## 💡타입과 인터페이스의 차이점 알기 |
| 46 | + |
| 47 | +타입스크립트에서 명명된 타입(named type)을 정의하는 두 가지 방법이 있습니다. |
| 48 | + |
| 49 | +1. type |
| 50 | + |
| 51 | +``` |
| 52 | +type TState = { |
| 53 | +name: string; |
| 54 | +capital: string; |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +2. interface |
| 59 | + |
| 60 | +``` |
| 61 | +interface Istate { |
| 62 | +name: string; |
| 63 | +capital: string; |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +대부분의 경우 두 가지 중 어느 것을 사용해도 상관 없습니다. 하지만 둘의 차이를 분명하게 알고, 같은 상황에서는 동일한 방법으로 명명된 타입을 정의해 일관성을 유지해야 합니다. |
| 68 | + |
| 69 | +그럼 둘의 차이가 뭘까요? |
| 70 | + |
| 71 | +인터페이스는 유니온 타입 같은 복잡한 타입을 확장하지는 못한다는 것입니다. 복잡한 타입을 확장하고 싶다면 타입과 &을 사용해야 합니다. 유니온 타입은 있지만, 유니온 인터페이스라는 개념은 존재하지 않죠. 그래서 type 키워드는 일반적으로 interface보다 쓰임새가 많습니다. type 키워드는 유니온이 될 수도 있고, 매핑된 타입 또는 조건부 타입 같은 고급 기능에 활용되기도 합니다. |
| 72 | + |
| 73 | +대신 인터페이스에는 타입에 없는 몇 가지 기능이 있습니다. 그중 하나가 바로 보강(augment)이 가능하다는 것입니다. 아까 interface를 설명할 때 등장했던 예제에 population 필드를 추가할 때 보강 기법을 사용할 수 있습니다. |
| 74 | + |
| 75 | +``` |
| 76 | +interface Istate { |
| 77 | +name: string; |
| 78 | +capital: string; |
| 79 | +} |
| 80 | +
|
| 81 | +interface Istate { |
| 82 | +population: number; |
| 83 | +} |
| 84 | +
|
| 85 | +const wyoming: IState = { |
| 86 | +name: 'Wyoming', |
| 87 | +capital: 'Cheyenne', |
| 88 | +population: 500_000 |
| 89 | +}; |
| 90 | +
|
| 91 | +``` |
| 92 | + |
| 93 | +이 예제처럼 속성을 확장하는 것을 '선언 병합(declaration merging)'이라고 합니다. 선언 병합을 지원하기 위해서는 반드시 인터페이스를 사용해야 합니다. |
| 94 | + |
| 95 | +자, 그럼 타입과 인터페이스 중 어느 것을 사용하는 게 좋을까요? |
| 96 | + |
| 97 | +복잡한 타입이라면 고민할 것도 없이 타입 키워드를 사용하면 됩니다. 그러나 두 가지 방법으로 모두 표현할 수 있는 간단한 객체 타입이라면 일관성과 보강의 관점에서 고려해 봐야 합니다. 기존의 코드베이스에서 사용하는 키워드를 쓰는 것이 좋습니다. |
| 98 | + |
| 99 | +## 💡 타입 연산과 제너릭 사용으로 반복 줄이기 |
| 100 | + |
| 101 | +같은 코드를 반복하지 말라는 DRY(don't repeat yourself) 원칙이 있습니다. 이 원칙은 타입에도 적용됩니다. 같은 타입을 반복해서 적어줄 필요는 없겠죠! 반복을 줄이는 가장 간단한 방법은 타입에 이름을 붙이는 것입니다. 예를 들어, 아래 코드처럼 몇 개의 함수가 같은 타입 시그니처를 공유하고 있다고 해 보겠습니다. |
| 102 | + |
| 103 | +``` |
| 104 | +function get(url:string, opts: Options) : Promise<Response> {} |
| 105 | +function post(url:string, opts: Options) : Promise<Response> {} |
| 106 | +``` |
| 107 | + |
| 108 | +위와 같은 경우 해당 시그니처를 명명된 타입으로 분리할 수 있습니다. |
| 109 | + |
| 110 | +``` |
| 111 | +type HTTPFunction = (url:string, opts: Options) => Promise<Response>; |
| 112 | +const get: HTTPFunction = (url, opts) => {}; |
| 113 | +const post: HTTPFunction = (url, opts) => {}; |
| 114 | +
|
| 115 | +``` |
| 116 | + |
| 117 | +한 인터페이스가 다른 인터페이스를 확장하게 해서 반복을 제거할 수도 있습니다. |
| 118 | + |
| 119 | +``` |
| 120 | +interface Person { |
| 121 | +firstName: string; |
| 122 | +lastName: string; |
| 123 | +} |
| 124 | +
|
| 125 | +interface PersonWithBirthDate extends Person { |
| 126 | +birth: Date; |
| 127 | +} |
| 128 | +``` |
| 129 | + |
| 130 | +이처럼 extends를 사용하면 인터페이스 필드의 반복을 피할 수 있습니다. |
| 131 | + |
| 132 | +뿐만 아니라 우리에게는 제너릭이 있습니다. 제너릭 타입은 타입을 위한 함수와 같습니다. 그리고 함수는 코드에 대한 DRY 원칙을 지킬 때 유용하게 사용됩니다. 따라서 타입의 반복을 줄이는 핵심에 제너릭이 있습니다. 타입을 반복하는 대신 제너릭 타입을 사용하여 타입들 간에 매핑을 하는 것이 좋습니다. 그런데 함수에서 매개변수로 매핑할 수 있는 값을 제한하기 위해 타입 시스템을 사용하는 것처럼, 제너릭 타입에서 매개변수를 제한할 수 있는 방법이 필요합니다. 그 방법은 바로 extends입니다. extends를 이용하면 제너릭 매개변수가 특정 타입을 확장한다고 선언할 수 있습니다. |
0 commit comments