1. 함수
타입스크립트의 함수는 Javascript와 마찬가지로 가명 함수(named function)과 익명 함수(anonymous function)로 만들 수 있습니다. 이를 통해서 API 함수 목록을 작성하거나 일회성 함수를 사용해 다른 함수로 전달해 애플리케이션에 가장 적합한 방법을 찾을 수 있습니다.
// 가명 함수
function add(x, y) {
return x + y;
}
// 익명 함수
let myAdd = function (x, y) {
return x + y;
};
함수는 함수 외부의 변수를 참조할 수 없습니다. 이런 경우, 변수를 캡쳐(capture) 한다고 합니다. 이것이 어떤 방식으로 작동하는지 이해하려면 매커니즘이 어떻게 작동하는지 아는 것이 중요합니다.
let z = 200;
function addToz(x, y) {
return x + y + z;
}
2. 함수 타입 (Function Types)
함수 타이핑
function add(x: number, y: number): number {
return x + y;
}
let Add = function (x: number, y: number): number {
return x + y;
};
각 피라미터와 함수 자신의 반환될 타입을 정할 수 있고, 타입스크립트는 반환 문을 보고 반환 타입을 파악할 수 있어 반환 타입을 생략할 수 있습니다.
3.함수 타입 작성하기
함수에 타입을 붙였다면, 이제 함수 타입들을 보고 함수의 전체 타입을 작성해 봅니다.
let myAdd: (x: number, y: number) => number = function (
x: number,
y: number
): number {
return x + y;
};
함수의 타입은 매개변수의 타입과 반환 타입이 있고, 전체 함수 타입을 작성하려면 두 가지 타입이 필요합니다. 매개 변수 목록처럼 각 매개변수에 이름과 타입의 타입을 작성해줍니다.
let myAdd: (baseValue: number, increment: number) => number = function (
x: number,
y: number
): number {
return x + y;
};
매개변수의 타입들이 올바르게 나열되어 있다면 함수 타입에 이름을 붙이더라도 유효한 타입으로 간주합니다. 두 번째로 반환 타입입니다. 매개변수 타입들과 반환 타입 사이 '화살표 표기'를 사용해 반환 타입을 분명히 할 수 있습니다. 만약 함수 값을 반환하지 않을 시 비워두는 대신 void를 사용해서 표시합니다.
4. 타입의 추론(Inferring the types)
TypeScripts 컴파일러가 방정식의 한쪽에만 타입이 있더라도 타입을 알아낼 수 있습니다.
// 전체 함수 타입을 가짐
let sideAdd = function (x: number, y: number): number {
return x + y;
};
// 매개변수 x와 y는 number 타입을 가짐
let myAdd: (baseValue: number, increment: number) => number = function (x, y) {
return x + y;
};
이러한 타입 추론 형태를 "contextual typing" 이라 부르고, 이를 통해서 프로그램에 타입을 유지하기 위한 노력을 줄일 수 있습니다.
5. 선택적 매개변수와 기본 매개변수 (Optional and Default Parameter)
타입스크립트에 모든 매개변수는 함수에 필요하다 가정합니다. 이것이 null 값이나 undefined를 줄 수 없다는 걸 의미하는 것은 아닙니다. 대신 함수가 호출될 때, 컴파일러는 각 매개변수에 대해 사용자가 값을 제공했는지 검사합니다.
또한, 컴파일러는 매개변수들이 함수로 전달될 유일한 매개변수라 가정하고, 함수에 주어진 인자의 수는 함수가 기대하는 매개변수의 수와 일치해야 합니다.
function buildName(firstName: string, lastName: string) {
return firstName + ' ' + lastName;
}
let result = buildName('Hole'); // Error 적은 매개변수
let result2 = buildName('Hole', 'Rike', 'Kiko'); // Error 많은 매개변수
let result3 = buildName('Bob', 'Adams'); // 정확한 매개변수
Javascript에 모든 매개변수는 선택적이고, 사용자가 적합하다고 판단되면 그대로 둘 수 있습니다. 그렇게 둔다면 그 값은 undefined 값이 됩니다. TypeScript 에서도 선택적 매개변수를 원한다면 매개변수 이름 끝에 ?를 붙여 해결할 수 있습니다.
function buildName(firstName: string, lastName: string) {
if (lastName) return firstName + ' ' + lastName;
else return firstName;
}
let result = buildName('Hole'); // 지금은 동작
let result2 = buildName('Hole', 'Rike', 'Kiko'); // Error 많은 매개변수
let result3 = buildName('Bob', 'Adams'); // 정확한 매개변수
어느 선택적 매개변수든 반드시 매개변수 정의가 필요합니다. lastName 대신 firstName을 선택적으로 하고 싶다면 매개변수 순서를 변경해야 합니다. Typescript에서는 유저가 값을 제공하지 않거나 undefined일때 할당된 매개변수의 값을 정해 놓을 수도 있습니다. 이것을 기본-초기화 매개변수라 합니다.
function buildName(firstName: string, lastName = 'Switch') {
return firstName + ' ' + lastName;
}
let result = buildName('Hole'); // 올바르게 동작, "Hole Switch"
let result2 = buildName('Hole', undefined); // 동작 "Hole Switch" 반환
let result3 = buildName('Bob', 'Adams', 'Sr.'); // Error, 너무 많은 매개변수
let result4 = buildName('Bod', 'Adams'); // 정확함
모든 필수 매개변수 뒤에 기본-초기화 매개변수는 선택적 처리되며, 선택적 매개변수와 마찬가지로 해당 함수를 호출할 때 생략할 수 있습니다. 이는 선택적 매개변수와 뒤따르는 기본 매개변수의 타입들이 공통성을 공유함을 의미합니다.
function buildName(firstName: string, lastName?: string) {
// ...
}
이 부분과
function buildName(firstName: string, lastName = "Switch") {
// ...
}
이 부분은 (firstName: string, lastName?: string) => string이라는 공통된 타입을 공유하고, lastName의 기본값은 타입에서 사라져 오직 선택적 매개변수라는 사실만 남깁니다. 순수한 선택적 매개변수와 다르게 기본-초기화 매개변수는 필수 매개변수 뒤에 오는 것이 강요되지 않습니다.
만약 기본-초기화 매개변수가 필수 매개변수보다 앞에 오면 명시적으로 undefined를 전달해 주어야 기본-초기화 매개변수를 볼수 있습니다.
function buildName(firstName = 'Hero', lastName: string) {
return (firstName = ' ' + lastName);
}
let result = buildName('Bob'); // Error 적은 매개변수
let reuslt2 = buildName('Bod', 'Adams', 'Sr.'); // 너무 많은 매개변수 Error
let result3 = buildName('Bob', 'Adams'); // 성공 "Bob Adams" 반환
let result4 = buildName(undefined, 'Adams'); // 성공 "Hero Adams" 반환
6.나머지 매개변수 (Rest Parameters)
필수적,선택적 기본 매개변수는 한 번에 하나의 매개변수만을 가집니다. 때로는 다수의 매개변수를 그룹을 지어 작업하기를 원하거나, 함수가 최종적으로 얼마나 많은 매개변수를 취할지 모를 때도 있습니다. JS에서는 모든 함수 내부에 위치한 arguments라는 변수를 사용해 직접 인자를 가지고 작업할 수 있습니다.
TypeScript에서는 이 인자들을 하나의 변수로 모을 수 있습니다.
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + ' ' + restOfName.join(' ');
}
// restName은 "Kora Kiko Luci Picus"가 됨
let restName = buildName('Kora', 'Kiko', 'Lucio', 'Picus');
console.log(restName);
나머지 매개변수는 선택적 매개변수들의 수를 무한으로 취급하고, 나머지 매개변수로 인자들을 넘겨줄 때 원하는 만큼 넘겨줄 수 있습니다. 아무것도 넘겨주지 않아도 됩니다. 컴파일러는 생략 부호 ( ... )뒤의 이름으로 전달된 인자 배열을 빌드해 함수에서 사용할 수 있습니다.
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + ' ' + restOfName.join(' ');
}
let restName: (fname: string, ...rest: string[]) => string = buildName;
7.this
TypeScript는 JS의 상위 집합으로 개발자들 역시 this가 어떻게 쓰이는지 또는 this가 잘못 쓰일 때 발견하는 방법을 배울 필요가 있습니다. Typescript는 몇가지 기술로 잘못된 this 사용을 잡아낼 수 있습니다.
this와 화살표 함수(this and arrow functions)
JS에서 this는 함수가 호출될 때 정해지는 변수이며, 매우 강력하고 유연한 기능이나 이것은 항상 함수가 실행되는 콘텍스트에 대해 알아야 합니다. 특히 함수를 반환하거나 인자로 넘길 때 혼란스러울 수 있습니다.
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
return function() {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
}
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit);
createCardPicker가 자기 자신을 반환하는 함수인데, 이 예제를 작동시키면 오류가 발생합니다. createCardPicker에 의해 생성된 함수에 사용중인 this가 deck 객체가 아닌 window에 설정되어 있습니다. cardPicker()의 자체적인 호출 때문에 생긴일로, 최상위 레벨에서 비-메서드 문법의 호출은 this 대신 window로 합니다.
이 문제는 나중에 사용할 함수를 반환하기 전 바인딩을 알맞게 하는 것으로 해결할 수 있습니다. 이 방법대로면 나중에 사용하는 방법에 상관없이 원본 deck 객체를 계속해서 볼 수 있습니다. 이를 위해 함수 표현식을 ES6의 화살표 함수로 바꿀 것입니다. 화살표 함수는 함수가 호출 된 곳이 아닌 함수가 생성된 쪽 this를 캡쳐합니다.
let deck = {
suits: ['hearts', 'spades', 'clubs', 'diamonds'],
cards: Array(52),
createCardPicker: function () {
// 아랫줄은 화살표 함수로, 'this'를 이곳에 캡쳐
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
},
};
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log('card:' + pickedCard.card + ' of ' + pickedCard.suit);
--noImplicitThis 플래그를 컴파일러에 전달하는 실수를 하면 TypeScript는 경고를 표시합니다. this.suits[pickedSuit]의 this는 any 타입입니다.
this 매개변수 (statement)
this.suits[pickedSuit]의 타입은 여전히 any 며, this가 객체 리터럴 내부의 함수에서 왔기 때문입니다. 이것을 고치려면 명시적으로 this 매개변수를 줄 수 있습니다. this 매개변수는 함수의 매개변수 목록에 가장 먼저 나오는 가짜 매개변수입니다.
function f(this: void) {
// 독립형 함수는 'this'를 사용할 수 없는 것을 확인
}
명확하고 재사용하기 쉽게 Card와 Deck 두 가지 인터페이스 타입들을 추가해 봅니다.
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
suits: ['hears', 'space', 'clubs', 'director'],
cards: Array(52),
// 아래 함수는 이제 callee가 반드시 Deck 타입이어야 함을 명시적 지정
createCardPicker: function (this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
},
};
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log('card: ' + pickedCard.card + ' of ' + pickedCard.suit); //card: 3 of hears
이제 TypScript에는 createPicker가 Deck 객체에 호출된다는 것을 알았고, 이것은 this가 any 타입이 아니라 Deck 타입이며 따라서 --noImplicitThis 플래그가 어떤 오류도 일으키지 않다는 것을 의미합니다.
8.콜백에서 this 매개변수
나중에 호출할 콜백 함수를 라이브러리에 전달할 때 this 떄문에 오류가 발생할 수 있습니다. 라이브러리는 콜백을 일반 함수처럼 호출하므로 this는 undefined가 됩니다. 일부 작업에 this 매개변수를 콜백 오류를 막는데 사용할 수 있습니다. 먼저 라이브러리 작성자는 콜백 타입을 this로 표시해 주어야 합니다.
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
this:void는 addClickListener가 onclick이 this 타입을 요구하지 않는 함수가 될 것으로 예상하는 것을 의미합니다. 두 번째로 호출 코드를 this로 표시합니다.
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// 콜백을 쓰면 런타임에 충돌
this.info = e.message
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad) // Error
this로 표시한 상태에서 onClickBad가 반드시 Handler의 인스턴스로 호출되어야 함을 명시해 주어야 합니다. 그러면 TypeScript 에서는 addClickListener 가 this: void를 갖는 함수를 필요로 한다는 것을 감지합니다. 오류를 고치려면 this 타입을 바꿔줍니다.
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// void 타입으로 this는 이곳에서 쓸 수 없음
console.log('clicked');
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad);
onClickGood이 this 타입을 void로 지정하고 있어 addClickListener로 넘겨지는데 적합합니다. 물론 이것이 this.info를 쓸 수 없는 것을 의미합니다. 만약 this.info까지 원한다면 화살표 함수를 써야합니다.
class Handler {
info: string;
onClickGood = (e: Event) => {
this.info = e.message;
};
}
이러한 작업은 화살표 함수의 외부에 this를 사용해 가능하며 this:void일 것으로 기대하는 무언가라면 전달에는 문제가 없습니다. handler 타입 객체마다 하나의 화살표 함수가 작성된다는 것이 단점으로, 반면 메서드들은 하나만 작성되 Handler의 프로토타입에 붙습니다. 그들은 Handler 타입의 모든 객체 간에 공유됩니다.
9. 오버로드 (Overloads)
Javascript는 본질적으로 매우 동적인 언어로, 하나의 함수가 전달된 인자의 형태의 따라 타입의 객체들을 반환하는 것은 흔한 일입니다.
let suits = ['hearts', 'speed', 'clubs', 'docker'];
function pickCard(x: string | number | any[]): any {
// 인자가 배열 또는 객체인지 확인 맞다면, deck을 주고 card 선택
if (typeof x == 'object') {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// 그렇지 않다면 그냥 card를 선택
else if (typeof x == 'number') {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [
{ suit: 'diamonds', card: 2 },
{ suit: 'spades', card: 10 },
{ suit: 'hearts', card: 4 },
];
let pickedCard1 = myDeck[pickCard(myDeck)];
console.log('card:' + pickedCard1.card + 'of' + pickedCard1.suit);
let pickedCard2 = pickCard(15);
console.log('card: ' + pickedCard2.card + ' of ' + pickedCard2.suit);
여기에는 사용자가 전달하는 것에 따라 두 가지 다른 결과를 반환하는 함수입니다. 사용자가 deck을 의미하는 객체 값을 전달한다면 함수가 카드를 선택하고, 사용자가 카드를 선택하면 선택한 카드가 무엇인지 대답해 줍니다. 하지만 타입 시스템이서 이것을 구현하는 방법을 알아야합니다.
오버로드 목록으로 동일한 함수에 다중 함수 타입을 제공해줍니다. 오버로드 목록은 컴파일러가 함수 호출들을 해결할때 사용하는 것이며, 오버로드 목록으로 pickCard가 동작을 승인해 반환하는 것을 구현해줍니다.
let suits = ['hearts', 'speed', 'clubs', 'docker'];
function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x: string | number | any[]): any {
// 인자가 배열 또는 객체인지 확인 맞다면, deck을 주고 card 선택
if (typeof x == 'object') {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// 그렇지 않다면 그냥 card를 선택
else if (typeof x == 'number') {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [
{ suit: 'diamonds', card: 2 },
{ suit: 'spades', card: 10 },
{ suit: 'hearts', card: 4 },
];
let pickedCard1 = myDeck[pickCard(myDeck)];
console.log('card:' + '' + pickedCard1.card + 'of' + pickedCard1.suit);
let pickedCard2 = pickCard(15);
console.log('card: ' + pickedCard2.card + ' of ' + pickedCard2.suit);
이 변화를 통해, 오버로드는 pickCard 함수에 대해 타입 검사 호출을 제공하고, 컴파일러가 알맞은 타입 검사를 위해, Javascript와 비슷한 프로세스를 따릅니다. 오버로드 목록에 첫 번째 오버로드를 진행하며 제공된 매개변수를 사용해 함수를 호출하려 시도합니다. 만약 일치하면 해당 오버로드를 알맞은 오버로드로 선택해 작업을 수행합니다.
이러한 이유로 가장 구체적인 것부터 오버로드 리스팅을 하는 것이 일반적입니다. 위 예제에 function pickCard(x) :any는 오버로드 목록에 해당되지 않습니다. 그래서 두 가지 오버로드만을 가집니다. 객체를 받는것 하나 숫자를 받는 것 하나. 다른 매개변수 타입으로 pickCard를 호출하는 것은 오류가 발생합니다.
참고자료
TypeScript 한글 문서
TypeScript 한글 번역 문서입니다
typescript-kr.github.io
GitHub - Koras02/typescript-bloging
Contribute to Koras02/typescript-bloging development by creating an account on GitHub.
github.com
'Front-End > TypeScript' 카테고리의 다른 글
[Typescript] 6장 유니언 타입과 교차 타입 (0) | 2025.02.18 |
---|---|
[TypeScript] 5강 리터럴 타입 (0) | 2025.02.15 |
[TypeScript] 3강 인터페이스 (0) | 2025.02.11 |
[Typescript] 2강 기본적인 타입 소개 (0) | 2025.02.09 |
[TypeScript] 1장. JavaScript의 종말 TypeScript 등장 (0) | 2025.02.08 |