오늘은 TypeScript 공부를 시작한지 2일차가 되었다. 오늘 공부한 내용을 정리해보려고 한다.
type Add = (a: number, b: number) => number;
const add: Add = (a, b)=> a + b;
함수가 서로 다른 여러 개의 call signatures를 가지고 있을 때 발생 시킨다.
⇒ call signatures란? 우리가 타입스크립트에게 이 함수가 어떻게 호출되는지 설명해주는 부분, 즉 위 코드에서 (a: number, b: number ⇒ number) 이렇게 선언해주는 부분처럼 파라미터의 타입은 무엇인지, 리턴 타입은 무엇인지 선언해주는 부분을 의미한다.
type Add = {
(a:number, b: number): number,
(a:number, b: string): number
}
const add: Add = (a, b) => a + b;

그냥 위 코드처럼 작성해주면 b는 number, 혹은 string이 될 수도 있어서 number 와 string은 “+” 를 할 수 없다고 해준다.
type Config = {
path: string,
state: object
}
type Push = {
(path: string) : void,
(config: Config) : void
}
const push: Push = (config) =>{
if(typeof config === "string") {
console.log(config);
}
else {
console.log(config.path)
}
}
config가 string인지, Config 타입인지 확인해주고, 해당하는 타입을 선택할 수 있다.
if문에서 string은 string 타입을, else문에서는 config 타입을 선택할 수 있다.
type Add = {
(a: number, b: number) : number,
(a: number, b: number, c: number) : number
}
const add: Add = (a, b, c) => {
return a + b
}
Add 타입을 부를 때, a, b 혹은 a, b, c를 부를 수 있기 때문에 타입 스크립트는 에러 메시지를 보내준다.
type Add = {
(a: number, b: number) : number,
(a: number, b: number, c: number) : number
}
const add: Add = (a, b, c?:number) => {
if(c) return a + b + c;
return a + b
}
add(1, 2)
add(1, 2, 3)
따라서, c는 optional하다는 것을 선언해주면 된다.
polymorphism은 여러가지 다른 구조들을 의미한다.
type SuperPrint = {
(arr: number[]) : void
(arr: boolean[]) :void
(arr: string[]) : void
}
const superPrint: SuperPrint = (arr)=>{
arr.forEach(i => console.log(i));
}
superPrint([1, 2, 3, 4]);
superPrint([true, false, true, true]);
superPrint(["a", "b", "c"]);
위 코드에서 문제점은 call signatures가 3개다.
만약 superPrint([1, 2, true, false]) 와 같은 코드를 쓰고 싶으면 위 코드는 작동하지 않는다.
→ 어떤 타입이든 동작하게 하고싶을 때는 어떻게 할까?
type SuperPrint = {
(arr: number[]) : void,
(arr: boolean[]) :void,
(arr: string[]) : void,
(arr: (number | boolean)[]) : void
}
const superPrint: SuperPrint = (arr)=>{
arr.forEach(i => console.log(i));
}
superPrint([1, 2, 3, 4]);
superPrint([true, false, true, true]);
superPrint(["a", "b", "c"]);
superPrint([1, 2, true, false])
위와 같이 number | boolean 조건을 추가해주면 된다.
하지만, 위와 같이 작성을 하게 된다면 모든 가능성을 다 조합해서 만들어야 하기 때문에 좋은 방법이 아니다.
그래서 generic 방식을 채택한다.
generic 방식은 타입스크립트가 그 타입이 뭔지 유추한다.
call signature를 작성할 때, 들어올 확실한 타입을 모를 때 generic을 사용한다.
혹은 call signature를 작성하는데 concrete type을 알 수 없을 때 사용한다.
⇒ concrete type란? number, string, boolean 등 타입을 의미한다.
type SuperPrint = {
<TypePlaceholder>(arr: TypePlaceholder[]) : void
}
위와 같이 <> 안에 제네릭 이름을 선언해주면 된다. 그리고 number 타입을 TypePlaceholder로 바꿔줘야 한다.
type SuperPrint = {
<TypePlaceholder>(arr: TypePlaceholder[]) : void
}
const superPrint: SuperPrint = (arr)=>{
arr.forEach(i => console.log(i));
}
superPrint([1, 2, 3, 4]);
superPrint([true, false, true, true]);
superPrint(["a", "b", "c"]);
superPrint([1, 2, true, false])
따라서, 타입 스크립트는 유추한 타입으로 call signature를 보여준다.
type SuperPrint = {
<T, M>(a: T[], b: M) : T
}
const superPrint: SuperPrint = (arr) => arr[0];
const a = superPrint([1, 2, 3, 4], "x");
위 코드와 같이 타입 스크립트는 제네릭을 처음 인식했을 때와 제네릭의 순서를 기반으로 제네릭의 타입을 알게 된다.
function superPrint<T>(a: T[]){
return a[0];
}
const a = superPrint([1, 2, 3, 4]);
const b = superPrint([true, false, true, true]);
const c = superPrint(["a", "b", "c"]);
const d = superPrint([1, 2, true, false])
이전에는 type을 선언해서 구현했었지만, 위와 같이 함수로 선언할 수도 있다.
제네릭을 사용해서 타입을 생성할 수도 있고 어떤 경우는 타입을 확장할 수도 있다.
type Player<E> = {
name: string,
extraInfo: E
}
type NicoExtra = {
favFood:string
}
type NicoPlayer = Player<NicoExtra>
const nico :NicoPlayer ={
name: "nico",
extraInfo:{
favFood: "kimchi"
}
}
위와 같이 타입을 생성하고 그 타입을 또 다른 타입에 넣어서 사용할 수도 있다.
즉, 타입끼리 재사용이 가능해진다.