JavaScript/TypeScript

TypeScript | 함수형 타입

pathas 2020. 5. 6. 15:43

함수 타입

함수의 타입을 정의하기 위해서는 매개변수 타입과 반환 타입이 필요
:(콜론)을 이용해서 매개변수 타입 및 반환 타입 정의

함수 타입 정의

function getInfoText(name: string, age: number): string {
    const nameText = name.substr(0, 10);
    const ageText = age >= 35 ? 'senior' : 'junior';
    return `name: ${nameText}, age: ${ageText}`;
}

const v1: string = getInfoText('mike', 23);
const v2: string = getInfoText('mike', '23'); // 타입 에러
const v3: number = getInfoText('mike', 23); // 타입 에러

console.log(v1); // name: mike, age: junior
  • 매개변수 name은 문자열 타입이기 때문에 substr 메서드 사용 가능
  • 매개변수 age는 숫자이기 때문에 다른 숫자와 크기 비교 가능
  • 매개변수 또는 반환 타입이 맞지 않는 경우 타입 에러 발생

변수를 함수 타입으로 정의

const getInfoText: (name: string, age: number) => string = function(name, age){
    // ...
    return `...`;
};
  • 함수를 구현하는 코드에서는 매개변수 타입과 반환 타입을 지정하지 않음
  • 타입스크립트는 function() 매개변수의 name과 age가 각각 문자열과 숫자라는 것을 알고 있음

선택 매개변수

반드시 입력하지 않아도 되는 매개벼수
매개변수 이름 오른쪽에 ?(물음표) 기호를 입력하면 선택 매개변수가 됨

function getInfoText(name: string, age: number, language?: string): string {
    const nameText = name.substr(0, 10);
    const ageText = age >= 35 ? 'senior' : 'junior';
    const languageText = language ? language.substr(0, 10) : '';
    return `name: ${nameText}, age: ${ageText}, language: ${languageText}`;
}

const v1 = getInfoText('mike', 23, 'ko');
const v2 = getInfoText('mike', 23);
const v3 = getInfoText('mike', 23, 123); // 타입 에러

console.log(v1); // name: mike, age: junior, language: ko
console.log(v2); // name: mike, age: junior, language: 
  • language를 선택 매개변수로 정의
  • 함수 호출 시 선택 매개변수의 인수를 입력하지 않아도 에러는 발생하지 않음
  • 함수 내부에서 language 인수의 존재 여부를 검사하지 않고 substr 메서드를 호출하면 타입 에러 발생
  • 선택 매개변수 오른쪽에 필수 매개변수가 오면 컴파일 에러 발생
// 컴파일 에러 발생
function getInfoText(name: string, language?: string, age: number): string {
    // ...
    return `...`;
}

undefined를 이용해서 중간에 선택 매개변수 정의하기

function getInfoText(
    name: string,
    language: string | undefined,
    age: number,
): string {
        // ...
        return `...`;
}
getInfoText('mike', undefined, 23);
  • |(유니온 타입)을 이용해서 undefined도 입력할 수 있도록 함
  • 함수 호출 시 중간에 undefined 입력
  • 컴파일 에러가 발생하지는 않지만 사용성과 가독성이 좋지 않음
    → 매개변수의 개수가 많은 경우에는 비구조화 문법을 이용해서 명명된 매개변수로 작성하는 게 좋음

매개변수 기본값 정의하기

function getInfoText(
    name: string,
    age: number = 15,
    language = 'korean',
): string {
    const nameText = name.substr(0, 10);
    const ageText = age >= 35 ? 'senior' : 'junior';
    const languageText = language ? language.substr(0, 10) : '';
    return `name: ${nameText}, age: ${ageText}, language: ${languageText}`;
}

console.log(getInfoText('mike'));
// name: mike, age: junior, language: korean

console.log(getInfoText('mike', 23));
// name: mike, age: junior, language: korean

console.log(getInfoText('jone', 36, 'english'));
// name: jone, age: senior, language: english

const f1: (
    name: string,
    age?: number,
    language?: string,
) => string = getInfoText;
  • 타입 오른쪽에 = 기호를 사용해서 매개변수 기본값 정의 가능
  • age의 인수를 입력하지 않으면 15가 기본값으로 사용됨
  • 타입을 입력하지 않아도 매개변수 기본값 정의 가능
    기본값이 문자열이기 때문에 매개변수 language의 타입도 문자열이 됨
  • 기본값이 있는 매개변수는 선택 매개변수

나머지 매개변수

나머지 매개변수의 타입은 배열로 정의 가능

function getInfoText(name: string, ...rest: string[]): string {
    // ...
    return `...`;
}

this 타입

함수의 this 타입을 정의하지 않으면 기본적으로 any 타입이 사용됨
any 타입은 가급적 쓰지 않는 게 좋으므로 타입을 정의해 두는 것이 좋음

this 타입을 정의하지 않은 경우

function getParam(index: number): string {
    const params = this.splt(',');
    if(index < 0 || params.length <= index){
        return '';
    }
    return this.split(',')[index];
}
  • this의 타입이 암묵적으로 any로 사용된다는 경고를 볼 수 있음
  • 2번째 줄에서 split을 splt로 오타를 냈지만 any 타입이기 때문에 컴파일 에러가 발생하지 않음

this 타입 정의하기

함수의 this 타입은 첫 번째 매개변수 위치에서 정의 가능

function getParam(this: string, index: number): string {
    const params = this.splt(','); // 타입 에러 발생
    // ...
    return '';
}
  • 매개변수 index는 두 번째 자리에 정의
  • 하지만 this 타입은 매개변수가 아니므로 index가 첫 번째 매개변수가 됨
  • this의 타입을 정의했기 때문에 splt 오타에서 타입 에러 발생

원시 타입에 메서드 추가하기

원시(primitive) 타입에 메서드를 등록할 때는 인터페이스 이용

interface String {
    getParam(this: string, index: number): string;
}

function getParam(this: string, index: number): string {
    const params = this.split(',');
    if(index < 0 || params.length <= index){
        return '';
    }
    return this.split(',')[index];
}

String.prototype.getParam = getParam;
console.log('asdf, 1234, ok'.getParam(1)); // 1234
  • 인터페이스를 이용해서 이미 존재하는 문자열 타입에 getParam 메서드 추가
  • 문자열의 프로토타입에 직접 작성한 getParam 함수 등록
  • 문자열에 등록된 getParam 메서드 호출 가능

함수 오버로드: 여러 개의 타입 정의하기

함수 오버로드(overload)를 사용하면 하나의 함수에 여러 개의 타입 정의 가능

함수 오버로드를 사용하지 않은 코드

숫자나 문자 값을 받아서 더한 뒤 매개변수가 모두 숫자면 숫자로, 그렇지 않은 경우에는 문자로 반환하는 함수

function add(x: number | string, y: number | string): number | string {
    if (typeof x === 'number' && typeof y === 'number'){
        return x + y ;
    } else {
        const result = Number(x) + Number(y);
        return result.toString();
    }
}

const v1: number = add(1, 2); // 타입 에러
console.log(1, '2');
  • 모든 매개변수가 숫자이므로 반환값도 숫자이지만 타입 에러 발생
  • 함수의 반환 타입을 구체적으로 정의하지 못했기 때문에 에러가 발생함

함수 오버로드를 사용한 코드

위의 함수에 오버로드를 적용한 함수
함수 오버로드를 적용할 때, 실제로 구현하는 함수를 가장 아래쪽에 작성

function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: number, y: string): string;
function add(x: string, y: number): string;
function add(x: number | string, y: number | string): number | string {
    if (typeof x === 'number' && typeof y === 'number'){
        return x + y ;
    } else {
        const result = Number(x) + Number(y);
        return result.toString();
    }
}

const v1: number = add(1, 2);
console.log(v1); // 3 -> number
console.log(add(1, '2')); // 3
console.log(add('1', 2)); // 3
console.log(add('1', '2')); // 3
  • 매개변수와 반환 타입의 모든 가능한 조합 정의
  • 실제로 함수를 구현하는 쪽에서 정의한 타입은 함수 오버로드의 타입 목록에서 제외됨
    → 실제로 구현하는 함수 바로 위에 [// @ts-ignore]를 작성한 후 any 타입으로 처리해도 됨
  • 함수 호출 시 어떤 경우에도 타입 에러가 발생하지 않음

명명된 매개변수

function getInfoText({
    name,
    age = 15,
    language,
}: {
    name: string,
    age?: number,
    language: string;
}): string {
    const nameText = name.substr(0, 10);
    const ageText = age >= 35 ? 'senior' : 'junior';
    return `name: ${nameText}, age: ${ageText}, language: ${language}`;
}

console.log(getInfoText({ name:'mike', age:23, language:'ko' }));
// name: mike, age: junior, language: ko
  • 먼저 모든 매개변수의 이름을 객체형으로 정의, 기본값이 있다면 이름과 함께 정의함
  • 앞에 나열된 모든 매개변수에 대한 타입 역시 객체형으로 정의
  • 명명된 매개변수를 사용하면 중간에 있는 매개변수를 선택 매개변수로 사용 가능
  • 비구조화 문법을 이용한 것이기 때문에 함수 호출 시에 인수로 객체를 전달해야 함
  • 명명된 매개변수의 타입을 다른 코드에서도 재사용하려면 인터페이스 사용

인터페이스로 명명된 매개변수 타입 정의

interface Param {
    name: string;
    age?: number;
    language?: string;
}
function getInfoText({ name, age = 15, language }: Param): string {
    // ...
    return '...';
}
  • 명명된 매개변수 타입을 인터페이스로 정의
  • 코드가 간소화되며 가독성이 높아지고 Param 인터페이스 재사용 가능

참고도서: [실전 리액트 프로그래밍 / 저자_ 이재승 / 출판사_ 프로그래밍 인사이트]