[TS] 타임 심화 1 (타입 별칭, 함수)

개발 log·2022년 1월 9일
0

TS 지식

목록 보기
7/15
post-thumbnail

조금 복잡한 타입에 대해서 다루는 글이며 내용이 길어지기 때문에 파트별로 나눠서 게시할 것이다.

타입 별칭

타입 별칭(type alias)을 이용하여 이미 존재하는 타입에 별칭을 붙여 복잡한 타입을 간단하게 쓸 수 있게 도와준다.

또한, 프로그래머의 의도를 보다 명확하게 나타낼 수 있다.

타입 별칭 정의

type NewType = Type;

별칭을 갖게 될 타입의 자리(Type)엔 기본 타입을 포함한 모든 타입이 올 수 있다.

type UUID = string;

type Height = number;

type AnotherUUID = UUID;

type Animals = Animal[];

type User = {
  name: string;
  height: number;
};

이 때 별칭은 이름만 재정의 하는 것이지 실제로 새로운 타입이 생성되는 것은 아니라는 점에 유의해야 한다.
예시로 아래와 같은 코드의 에러메시지에는 UUID 타입이 아닌 string 타입으로 에러메시지를 띄워준다.

type UUID = string;
function getUser(uuid: UUID) {
  /* ... */
}

// error TS2345: Argument of type '7' is not assignable to parameter of type 'string'.
getUser(7); 


함수

함수의 타입

함수의 타입을 결정하기 위해서는 아래의 두 가지 정보가 필요하다.

  • 매개변수(parameter)의 타입
  • 반환값(return value)의 타입(반환 타입)

매개변수

변수의 타입을 표기할 때와 마찬가지로 매개변수 뒤에 콜론(:)을 붙이고 타입을 적는다.

(param1: number, param2: string)

반환값

반환값의 타입은 매개변수 목록을 닫는 괄호())와 함수 본문을 여는 여는 대괄호({)사이에 콜론(:)을 붙이고 표기한다.

function (): number { ... }

일반적으로 반환값이 있는 형태는 아래와 같다.

function sum(a: number, b: number): number {
  return (a + b);
}

반환 값이 없는 경우 반환 타입으로 void를 사용한다.

function logGreetings(name: string): void {
  console.log(`Hello, ${name}!`);
}

void 반환 타입을 갖는 함수가 undefinednull 이외의 값을 반환하면 타입 에러다.
void가 아닌 반환 타입을 갖는 함수가 아무 값도 반환하지 않아도 타입 에러다.

function notReallyVoid(): void {
  return 1;
}
// error TS2322: Type '1' is not assignable to type 'void'.

function actuallyVoid(): number { }
// error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.

함수 값의 타입 표기

함수 표현식을 사용할 경우 함수 타입을 지정할 수 있다.

함수 타입의 값에 타입 표기를 붙이기 위해서는 화살표 함수 정의 문법과 비슷한 문법을 사용한다.

(...params) => type
() => type

예시는 아래와 같다.

const yetAnotherSum: (a: number, b: number) => number = sum;

const onePlusOne: () => number = () => 2;

const arrowSum: (a: number, b: number) => number = (a, b) => (a + b);

타입 별칭도 사용 가능하다.

type SumFunction = (a: number, b: number) => number;

const definitelySum: SumFunction = (a, b) => (a + b);

기본 매개변수

TS에서도 JS의 기본 매개변수 문법을 사용할 수 있다.

매개변수명: 타입 = 기본값

예시는 아래와 같다.

function greetings(name: string = 'stranger'): void {
  console.log(`Hello, ${name}`);
}
greetings('Heejong'); // Hello, Heejong!
greetings(); // Hello, stranger!

선택 매개변수

객체타입을 지정할 때처럼 함수의 매개변수도 선택 매개변수 지정이 가능하다.
사용법은 객체의 선택속성처럼 물음표(?)를 속성명 뒤에 붙여주면 된다.

function fetchVideo(url: string, subtitleLanguage?: string) {
  const option = { url };
  if (subtitleLanguage) {
    option.subtitleLanguage = true;
  }
  /* ... */
}
fetchVideo('https://example.com', 'ko'); // okay
fetchVideo('https://example.com'); // also okay

이 때 주의할 점은 매개변수 정의 순서에서 선택 매개변수 이후에 필수 매개변수를 두면 에러다.

function invalidFetchVideo(subtitleUrl?: string, url: string) {
  /* ... */
}
//error TS1016: A required parameter cannot follow an optional parameter.

함수 오버로딩

JS에서 한 함수가 여러 쌍의 매개변수-반환 타입 쌍을 갖는 경우가 꽤나 흔하다.

function (value) {
	if (typeof value === 'string') {
      return value.replace(/./,'');
    } else if (typeof value === 'number') {
      return value * 2
    }
}

이 때 이렇게 여러 쌍의 매개변수-반환 타입 쌍을 갖는 경우의 함수 타입을 정의할 수 있게 TS는 함수 오버로딩을 지원한다.

TS의 함수 오버로딩의 특징을 아래와 같다.

  • 함수는 하나 이상의 타입 시그니처를 가질 수 있다.
  • 함수는 단 하나의 구현을 가질 수 있다.

즉, 오버로딩을 통해 여러 형태의 함수 타입을 정의할 수는 있지만,
실제 구현은 한 번만 가능하기 때문에 타입에 대한 분기는 구현 시 함수 몸체에서 이뤄져야 한다.

// 함수 타입 시그니처
function double(str: string): string;
function double(num: number): number;
function double(arr: boolean[]): boolean[];

// 함수 구현
function double(arg) {
    if (typeof arg === 'string') return `${arg}${arg}`;
    else if (typeof arg === 'number') return arg * 2;
    else if (Array.isArray(arg)) return arg.concat(arg);
}

// 함수 호출
// 오버로딩을 통해 정의된 `double`함수는 호출하는 인자의 타입에 따라 반환 타입이 달라진다.
const num = double(3); // number
const str = double('ab'); // string
const arr = double([true, false]); // boolean[]

This 타입

JS를 공부했다면 this는 동적으로 바인딩 된다는 것을 알고 있을 것이다.
this의 동적 바인딩은 this의 타입을 추론하는 것을 매우 어렵게 만든다.

TS에서는 이러한 어려움을 해결하기 위해 함수 내에서의 this타입을 명시할 수 있는 수단을 제공한다.

함수의 this타입을 명시하기 위해서는 타입 시그니쳐에서 매개변수 가장 앞에 this를 추가해주면 된다.
이 때 this타입은 타입 시스템을 위해서만 존재하는 일종의 가짜 타입이다.
즉, this 매개변수를 추가해도 함수가 받는 인자 수와 실제 동작은 변하지 않는다.

interface HTMLElement {
  tagName: string;
  /* ... */
}
interface Handler {
  (this: HTMLElement, event: Event, callback: () => void): void;
}

let cb: any;

// 실제 함수 매개변수에는 this가 나타나지 않음
const onClick: Handler = function(event, cb) {
  // this는 HTMLElement 타입
  console.log(this.tagName);
  cb();
}

만약 this에 접근하는 일 자체를 막으려면 this의 타입을 void로 명시하면 된다.

interface NoThis {
  (this: void): void;
}
const noThis: NoThis = function() {
  console.log(this.a); // Property 'a' does not exist on type 'void'.
}


참고

ts-for-dev

profile
프론트엔드 개발자

0개의 댓글

관련 채용 정보