함수의 call signature, generic | 노마드코더 타입스크립트(2)

박예선·2022년 11월 14일
0

TypeScript

목록 보기
2/2

노마드코더 Typescript로 블록체인 만들기 강의 정리 - 2 (챕터3)👊 내 것으로 만들자


🎞️3-0강 Call Signatures

  • 인자의 타입과 함수의 반환값의 타입을 알려준다. 함수가 어떻게 구현되는지가 아닌, 어떻게 호출되는지 설명해주는 부분.
  • 함수 작성 전, 타입을 생각하도록 해준다.
  • 실질적인 코드와 타입 선언을 분리해준다.
type Add = (a:number, b:number) => number;  //함수의 call signature타입 지정

const add = (a:number, b:number) => a+b  //call signature 사용 전
const add:Add = (a,b) => a+b             //call signature 사용 후 

🎞️3-1강 Overloading

  • function overloading, method overloading 등 다양하게 불린다.
    오버로딩 된 함수를 직접 작성할 일이 많지 않지만 외부 패키지나 라이브러리에서 많이 사용되기 때문에 어떤 것인지 알고있어야 한다.
  • 오버로딩은 함수가 여러 call signatures를 가지고 있을 때 발생한다.

참고✔️
예제1과 2는 같은 call signature이다.

//예제 1
type Add = (a: number, b: number) => number

//예제 2
type Add = {
  (a: number, b: number) => number
}

1. call signature들의 매개변수의 개수가 같을 때

오버로딩의 예시
자주 볼 만한 예시는 아님.

type Add = {
  (a: number, b: number) : number
  (a: number, b: string) : number
}

//오류⛔
const add: Add = (a,b) => a+b
//작동✔️
const add: Add = (a,b) => {   
  if(typeof b === 'string') return a
  return a+b
}

자주 볼 법한 오버로딩의 예시

type Config = {
  path: string,
  state: object
}

type Push = {
  (path: string): void     //void: 아무것도 리턴하지 않는 것
  (config: Config): void
}

const push: Push = (config) => {       //confi
  //타입이 string일 때
 if(typeof config === "string") {console.log(config)}  
  //타입이 Config일 때
 else {                        
  console.log(config.path)    //또는 config.state도 가능
 }
}

2. call signature들의 매개변수의 개수가 다를 때

  • 보통 1번의 예시들이 더 자주 쓰이긴 하지만 매개변수(parameter)의 개수가 다른 경우의 오버로딩도 있다!
type Add = {
	(a: number, b: number): number;
	(a: number, b: number, c: number): number; //c는 옵션
};

//오류⛔
const add: Add = (a, b, c) => {   //c가 옵션이라는 것과 c의 타입을 알려줘야함.
	if (c) return a + b + c;
	return a + b;
};

//작동✔️
type Add = {
	(a: number, b: number): number;
	(a: number, b: number, c: number): number; //c는 옵션
};
const add: Add = (a, b, c?: number) => {
	if (c) return a + b + c;
	return a + b;
};

//작동✔️ 위의 예제 같은 기능이다.
type Add2 = {
	(a: number, b: number, c?: number): number; //c는 옵션
};
const add2: Add2 = (a, b, c) => {
	if (c) return a + b + c;
	return a + b;
};

아직 오버로딩이 어디에 어떻게 쓰일지는 예상이 안가지만 이해완료!


🎞️3-2강 Polymorphism(다형성)

poly : many, several, much, multi... morphos/porphic : form, structure...

➡️Polymorphism : 여러가지 구조(형태)
3-1강의 오버로딩도 어떻게보면 polyporphism의 한 형태이다.
generic을 사용해서 함수의 다형성을 표현한다.

  • generic을 사용하는 경우: 함수의 call signature를 작성할 때, 어떤 타입이 들어올지 확실하게 모르는 경우

    주의사항⚠️
    generic은 대문자로 시작해야한다.

1. 예제 1

superPrint: 배열을 인자로 받고, 그 배열의 요소들을 콘솔에 찍는 함수
배열의 요소로 어떤 타입이든 들어갈 수 있다.

const superPrint: SuperPrint = (arr) => {
  arr.forEach(i => console.log(i));
}

generic을 사용하지 않은 type

type SuperPrint = {
  (arr: number[]): void;
  (arr: boolean[]): void;
  ...
  ...
  ...
  //또는 모든 가능성을 고려해 타입을 예상해야 한다.
  (arr: (number|boolean ...)[]): void;
}

generic을 사용한 type

type SuperPrint = {
  <TypePlaceHolder>(arr: TypePlaceHolder[]): void;
  //<>안의 이름은 마음대로 지어도 됨. 주로 <T>라고 사용함
}

2. 예제 2

superPrint2: 배열을 인자로 받고, 그 배열의 첫번째 요소를 return하는 함수
배열의 요소로 어떤 타입이든 들어갈 수 있다.

const superPrint2: SuperPrint2 = (arr) => arr[0];

generic을 사용한 type

type SuperPrint2 = {
  <TypePlaceHolder>(arr: TypePlaceHolder[]): TypePlaceHolder;
  //리턴값의 타입을 void-> TypePlaceHolder로 바꿔주었다.
}

🎞️3-3강 Generics Recap

1. generic과 any의 차이

  • generic(placeholder)any와 다른 점은 우리가 요구하는 대로 call signature가 생성된다는 것이다. any를 사용하면 함수의 call signature가 자동으로 생성되지 않기 때문에 발생하는 오류를 인식할 수 없다.

    아래 예시를 type SuperPrint = (a:any[]) => any로 바꾼다면
    a.toUpperCase()는 정상작동한다. 즉, 오류를 미리 잡을 수 없다.

type SuperPrint = <T>(arr:T[]) => T;

const superPrint: SuperPrint = (arr) => {
  return arr[0];
}

const a = superPrint([1,2,3]);

a.toUpperCase(); //오류⛔
//generic을 사용하면 a의 타입이 number이기 때문에 미리 오류를 잡아주지만
//any를 사용하면 a의 타입이 any이기 때문에 오류를 잡지 못한다.

2. generic의 복수 사용

  • Alias 타입에 generic을 추가하고 싶다면? <>안에 원하는 이름을 추가하고
    추가한 generic이 어디에 쓰일 건지 말해주면 된다.
type SuperPrint = <T,M>(arr:T[], b:M) => T;

const superPrint: SuperPrint = (arr) => arr[0];
//함수의 리턴값에 따라 SuperPrint의 리턴 generic도 달라질 수 있다.

🎞️3-4강 Conclusions

  • 직접적으로 call signature를 작성하게 되는 경우보단 외부 라이브러리나 패키지를 사용할 때 이미 만들어진 제네릭과 call signature를 사용하게 될 것이다.
  • 또한 generic은 함수의 call signature을 생성하는 경우 외에도
    타입을 생성하고, 타입을 확장하고, 코드를 저장하는 데에 사용된다.

1. 제네릭을 함수에 사용하는 방법들

제네릭을 함수에 사용하는 방법 1

  • 함수의 Alias타입에 적용
type SuperPrint = <T>(a: T[]) => T;

const superPrint:SuperPrint = (a) => a[0];

제네릭을 함수에 사용하는 방법 2

  • 함수에 직접 적용
function superPrint<T>(a: T[]){
  return a[0];
}
//또는
const superPrint = <T>(a: T[]) => a[0];

2. 제네릭으로 타입 생성, 타입 확장, 코드 저장

  • 제네릭을 사용해서 타입을 생성하고, 그 타입을 또 다른 타입에 넣고 하는 과정을 통해
    원하는대로 타입 확장이 가능하다.
//예시 - 꼭 이렇게까지 확장하지 않아도 되지만 예시!
type Player<E> = {
	name: string;
	extraInfo: E;
};

type NicoExtra = {
	favFood: string;
};
//NicoPlayer타입안에 NicoExtra타입을 넣음
type NicoPlayer = Player<NicoExtra>;

const nico: NicoPlayer = {
	name: 'nico',
	extraInfo: {
		favFood: 'kimchi',
	},
};

3. 제네릭으로 타입을 만드는 또다른 방법

//위아래가 똑같은 타입이다
type A = number[];
type A = Array<number>;

타입스크립트가 점점 재밌다. 3챕터부터는 이해가 잘 안돼서 2번씩 본 부분도 있지만...
이제부터는 토이프로젝트로 직접 만들면서 들어보는걸로!

profile
개발 좋아 lynn08082@gmail.com

0개의 댓글