타입스크립트 - 함수

드엔트론프·2023년 8월 1일
0

typescript

목록 보기
6/12
post-thumbnail

함수 타입 표현식과 호출 시그니쳐

  • 호출 시그니쳐는 함수 매개변수와 타입을 정의하는 부분을 따로 떼어 정의해놓은걸 갖다 쓰는 모습을 호출 시그니쳐라고 부른다.
type Operation2 = {
  (a: number, b: number) : number
}

const add2: Operation2 = (a, b) => a + b;
  • 여기 add2에 정의된 Operation2처럼 { } 중괄호 안에 정의하거나,
type Add = (a: number, b: number) => number;

const add: Add = (a, b) => a + b;
  • add 처럼 중괄호 안하고 화살표로 정의할 수 있다.
  • 개인적으로 화살표로 하는 게 더 보기 편한듯하다.

함수 타입의 호환성

  • 함수는 매개변수의 타입과 반환값의 타입을 정해줄 수 있는데, 반환값은 기존 타입처럼 업캐스팅이 가능하지만, 다운캐스팅은 안된다.
  • 하지만
    매개변수를 비교할 때 기존에 타입으로 봤었던 다운캐스팅, 업캐스팅과는 반대로 다운캐스팅이 되고, 업캐스팅은 되지 않는다.

함수 오버로딩

  • 하나의 함수를 매개변수의 개수나 타입에 따라 여러가지 버전으로 만드는 문법이다.
  • 많은 라이브러리들을 보면 이런식으로 되어있으니 알아두면 좋다.
/**
 * 함수 오버로딩
 * 하나의 함수를 매개변수의 개수나 타입에 따라
 * 여러가지 버전으로 만드는 문법
 * -> 하나의 함수 func
 * -> 모든 매개변수의 타입 number
 * -> 매개변수가 1개 -> 매개변수 * 20
 * -> 매개변수 3개 -> 3개 다 더한값
 */

// 버전들 -> 오버로드 시그니쳐
function func(a: number): void;
function func(a: number, b: number, c: number): void;

//실제 구현 -> 구현 시그니쳐
function func(a: number, b?: number, c?: number) {
  if (typeof b === "number" && typeof c === "number") {
    console.log(a + b + c);
  } else {
    console.log(a * 20);
  }
}
  • 오버로드 시그니쳐를 이용해 하나의 함수에 여러 버전들을 만들어놓는다.
  • 실제 구현 - 구현 시그니쳐에서 매개변수에 모든 버전을 수용하기 위해 ?를 활용하고, 타입가드를 통해 원하는 동작이 되도록 하였다.

사용자 정의 타입 가드

  • 개와 고양이 타입이 있다고 하자. 사실 서로소 유니온 타입으로 지정하면 타입가드를 할 수 있으나, 어디 라이브러리라던가 내가 함부로 못바꾸는 타입 정의라고 가정하자.
type Dog = {
  name: string;
  isBark : boolean
}

type Cat = {
  name: string;
  isScratch : boolean
}

type Animal = Dog | Cat
function warning(animal : Animal){
  if("isBark" in animal){
    //dog
  }else if("isScratch" in animal){
    //cat
  }
}
  • 간단하게는 warning 함수를 위와같이 정의할 수 있다. 그치만,, 보기 별로고 직관적이지 않고 만약 isBark 이런거 바뀌면 또 어떻게 수정하나.. 매우 번거로운 일이 될 수 있다.
  • 그걸 해결하기 위해, 개인지 고양이인지를 알려주는 함수를 만들어 그 함수를 타입 가드로 대신하는 작업이다.
function isDog(animal: Animal){
  return animal.isBark !== undefined //'Animal' 형식에 'isBark' 속성이 없습니다. 'Cat' 형식에 'isBark' 속성이 없습니다.
}
  • 이렇게 하면 될거 같은데 빨간줄이 발생한다. animal이 dog인지 cat인지 모르는 상황이기 때문이다.
  • 그럴땐 return animal에 소괄호를 치고 타입 정의를 해주면 된다 !
function isDog(animal: Animal) {
  return (animal as Dog).isBark !== undefined; 
}
  • 이렇게 타입 정의를 해주면 모든 문제 해결 ~~~ 인줄 알았지만 아니다.
  • 여전히 warning 함수에서는 타입이 제대로 좁혀지지 않았다.
    animal is bark error
function warning(animal: Animal) {
  if (isDog(animal)) {
    //dog
    animal.isBark //'Animal' 형식에 'isBark' 속성이 없습니다. 'Cat' 형식에 'isBark' 속성이 없습니다.ts(2339)
  } else if ("isScratch" in animal) {
    //cat
    animal
  }
}
  • 분명 좁혔는데 왜 안될까? 이럴때 추가로 isDog 함수의 반환되는 값의 타입을 정확히 또 지정해주는 것이다.
function isDog(animal: Animal): animal is Dog {
  return (animal as Dog).isBark !== undefined; 
}
  • animal is Dogreturn문이 참일 때, 아 ~ animal은 Dog구나! 라고 알려주는 것이다.
  • 이렇게까지 하면, warning에서 타입가드를 제대로 좁힐 수 있게 된다.

no error

  • 에러 없는 모습.
  • isCat도 동일하게 작성하면 된다.
function isCat(animal: Animal) : animal is Cat{
  return (animal as Cat).isScratch !== undefined
}
  • 아래 완성 코드
function isCat(animal: Animal) : animal is Cat{
  return (animal as Cat).isScratch !== undefined
}

function isDog(animal: Animal): animal is Dog {
  return (animal as Dog).isBark !== undefined; 
}

function warning(animal: Animal) {
  if (isDog(animal)) {
    //dog
    animal.isBark 
  } else if (isCat(animal)) {
    //cat
    animal.isScratch
  }
}

마치며

  • 지난 글인 서로소 유니온 타입에서 명확한 타입 좁히기를 통해 더 알아보기 쉬운 타입으로 이해할 수 있었듯, 함수 타입 좁히기도 타입 정의만 잘 해준다면 더 이해하기 쉬운 타입 코드가 될 수 있다는 것을 알았다.
profile
왜? 를 깊게 고민하고 해결하는 사람이 되고 싶은 개발자

0개의 댓글