✔️

03. Functions

함수는 자바스크립트 Application의 기본적인 구성요소로

추상화 계층을 구축하고, 클래스를 모방하거나 정보 은닉, 모듈에 대한 방법을 제공함

타입스크립트에서는 함수의 작업을 더 수월하게 하기 위해 몇 가지 추가적인 기능을 제공함

자바스크립트와 마찬가지로 함수를 익명/기명으로 생성할 수 있음

// 기명 함수(named function)
function add(x, y) {
  return x + y;
}

// 익명 함수(anonymous function)
let myAdd = function(x, y) { return x + y; };

자바스크립트 함수에서 함수 바깥의 변수에 접근할 수 있고 이를 캡쳐 한다고 표현함

이 동작 방식을 알고 있는 것이 앞으로의 내용을 이해하는데 중요

let z = 100;

function addToZ(x, y) {
  return x + y + z;
}

함수 타입(Function Types)

함수에 타입을 넣기(Typing the function)

function add(x: number, y: number): number {
  return x + y;
}

let myAdd = function(x: number, y: number): number { 
	return x + y; 
};

위에서 생성한 자바스크립트 함수의 기본적인 형태에 파라미터, 리턴 값에 타입을 추가할 수 있음

return 문을 보고 타입을 추론할 수 있기 때문에 리턴 값의 타입은 생략할 수 있음

개인적으로 써주는게 좋다고 생각.. 시그니처만 보고 알 수 있는게 코드 이해하기가 수월함

함수타입 작성하기(Writing the function type)

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;
};

타입 추론(Inferring the types)

한쪽에만 타입이 있어도 컴파일러가 알아서 타입을 추론함

솔직히 이건 필수적인 부분이라고 생각함; 위에 두 번 쓰는거 보고 DRY하지 못함에 분노..

// myAdd는 전체 함수 타입을 가짐
let myAdd = 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" 라고 부름

이를 통해서 프로그램에서 타입을 유지하기 위한 수고를 덜 수 있음 (아니었음 타입스크립트 때려침;)

선택적/기본 파라미터(Optional and Default Parameters)

타입스크립트에서는 모든 파라미터가 함수에 필요하다고 가정함

파라미터에 null이나 undefined를 줄 수 없다는 것이 아니라,

컴파일 단계에서 해당 함수를 호출하는 지점이 파라미터에 명시된 타입의 값을 넘겼는지 확인한다는 것

만약 null이나 undefined를 파라미터에 지정했다면 그렇게 넘겼는지를 봄

선택적 파라미터

자바스크립트에서처럼 선택적으로 파라미터를 넘기고 싶다면 (필요할 땐 넘기고 아니면 안넘김 = undefined)

매개변수의 이름 끝에 ?를 붙여서 해당 파라미터를 선택적으로 만들 수 있음

기본 파라미터

값을 제공하지 않았거나 undefined를 받았을 때 할당할 값을 지정 가능

자바스크립트에서 파라미터 기본 값을 지정하는거랑 똑같음

기본 파라미터를 지정한 경우 result1을 보면 선택적 파라미터처럼

파라미터를 넘기지 않아도 되는 것을 알 수 있음

= 선택적 파라미터와 동일하게 firstName: string, lastName?: string => string 타입을 가짐

선택적 파라미터와 다른 점은 꼭 맨 뒤에 위치하지 않아도 됨

Rest 파라미터

interface somtt {
   [asdfasdf: string]: boolean | string | ..;;
}
function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

...으로 rest 파라미터를 추가할 수 있고, 그 타입은 배열형의 타입을 지정하듯이 하면 됨

function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}

let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;

함수 타입을 만들 때도 똑같이 사용할 수 있음

this

this와 화살표 함수

자바스크립트에서 this는 함수가 호출될 때 설정됨

이런 바인딩이 강력하고 유연하지만 함수가 호출되는 context를 항상 잘 알아야하는 어려움이 있음

특히 함수를 파라미터로 넘기거나 반환하는 경우에 혼란을 유발할 수 있음

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();

console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
let deck = {
  suits: ["hearts", "spades", "clubs", "diamonds"],
  cards: Array(52),
  createCardPicker: function () {
    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 옵션을 컴파일러에 전달하면 위 경우 경고를 띄움

this 파라미터

위의 예제에서 this의 타입은 여전히 any임 (deck를 가리키니까 타입도 deck였으면 좋겠는데 아님)

이유는 객체 리터럴 내부에 있는 함수에서 시작된 것이기 때문

→ 이 문제를 해결하기 위해서 명시적으로 this 파라미터를 전달할 수 있음

function f(this: void) {
  // 독립형 함수에서 `this`를 사용할 수 없는 것을 확인함
}
interface Card {
  suit: string;
  card: number;
}

interface Deck {
  suits: string[];
  cards: number[];
  createCardPicker(this: Deck): () => Card;
}

let deck: Deck = {
  suits: ["hearts", "spades", "clubs", "diamonds"],
  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 
      };
    }
  }
};

callback 함수에서의 this 파라미터

사용하는 라이브러리에서 나중에 호출할 수 있도록 콜백함수를 전달할 때도

this로 인한 오류가 발생할 수 있음

→ 라이브러리에서 콜백함수를 일반 함수처럼 호출해서 thisundefined가 될 수 있음

interface UIElement {
	addClickEventListener(onclick: (this: void, e: Event) => void): void;
}
/* 나쁜 예제 */
class Handler {
  info: string;
  onClickBad(this: Handler, e: Event) {
    // 여기에서 this를 사용했으니 runtime 호출 시에 오류가 날 것
    this.info = e.message;
  }
}

let h = new Handler();
uiElement.addClickListener(h.onClickBad);


/* 좋은 예제 */
class Handler {
  info: string;
  onClickGood(this: void, e: Event) {
    // this는 void 타입이기 때문에 이 함수 내부에서 this를 이렇게 안써야 함
    console.log("clicked!");
  }
}

let h = new Handler();
uiElement.addClickListener(h.onClickGood);

this를 쓰고 싶다면 화살표 함수를 이용해야 함

class Handler {
	info: string;
	onClickGood = (e: Event) => {
		this.info = e.message;
	};
}

Overload

자바스크립트는 선천적으로 매우 동적인 언어로,

하나의 자바스크립트 함수가 파라미터에 따라서 다른 타입의 객체를 반환하는 것은 흔한 일임

자바스크립트에는 오버로드가 없다

ref. https://kevinkreuzer.medium.com/typescript-method-overloading-c256dd63245a

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: any): any {
  if (typeof x == "object") {
    let pickedCard = Math.floor(Math.random() * x.length);
    return pickedCard;                                  // number
  }
  else if (typeof x == "number") {
    let pickedSuit = Math.floor(x / 13);
    return { suit: suits[pickedSuit], card: x % 13 };   // object
  }
}

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가 리턴하는 값의 타입이 달라짐

이것을 어떻게 타입 시스템으로 표현할 것인가..

→ 같은 이름의 함수를 여러 개 제공하면 됨 (오버로드)

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x: any): any {
  if (typeof x == "object") {
    let pickedCard = Math.floor(Math.random() * x.length);
    return pickedCard;
  }
  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);