코딩/Typescript

타입스크립트 함수 알아보기 - 타입스크립트 TypeScript 강좌 2편

드리프트 2021. 10. 23. 14:57
728x170

 

 

 

안녕하세요?

 

지난 시간부터 시작한 타입스크립트(typescript) 강좌를 이어 나가 보겠습니다.

 

일단 지난 시간에 설정한 NodeJS 패키지를 그대로 이어서 진행할 예정이오니 1편에서 typescript와 ts-node 설정부터 살펴보시면 감사하겠습니다.

 

 

1. function in typescript

 

일단 함수를 만들어야겠죠.

 

먼저, functions.ts 파일을 만들고 이 파일을 이용한 functions-test.ts 파일도 만들어서 여기서 함수 테스트를 해보겠습니다.

 

먼저 간단한 함수를 만들어 보겠습니다.

 

//functions.ts

function addNumbers(a, b) {
    return a + b;
}

export default addNumbers;

타입스크립트는 ES Module 방식을 따르기 때문에 module.exports 방식이 아닌 export default 방식으로 써야 합니다.

 

제가 예전에 썼던 글을 보시면 자바스크립트 모듈 방식을 이해하실 수 있을 겁니다.

 

https://cpro95.tistory.com/505

 

자바스크립트 Javascript ES6 NodeJS 모듈 Module exports export default require import 이해

안녕하세요? 오늘은 자바스크립트나 NodeJS에서의 모듈(Module)에 대해 살펴보겠습니다. 먼저, 요즘 프런트엔드 쪽에서 많이 쓰는 React나 Vue 등은 NodeJS를 사용해서 웹페이지를 만드는데요. 그래서

cpro95.tistory.com

 

 

그럼 functions-test.ts 파일에서 위에서 만든 함수를 테스트해 볼까요?

 

// functions-test.ts

import addNumbers from "./functions";

console.log(addNumbers(1, 2));
console.log(addNumbers(1, "squid"));

 

이 파일은 VS Code에서 보면 에러 체크가 안보입니다.

 

왜 그럴까요?

 

그러면 한번 ts-node로 실행해 볼까요?

 

컴파일 에러가 나왔습니다.

 

왜 그럴까요?

 

addNumbers 함수의 파라미터들이 컴파일러가 볼 때 타입이 any라는 겁니다.

 

타입스크립트에서 any 타입은 어떤 타입이어도 되기 때문에 당연히 우리가 지정한 addNumbers(1, "squid") 함수 실행은 문제없이 진행됩니다.

 

왜냐하면 addNumbers의 파라미터 a, b가 any 타입으로 추론되기 때문에 number도 가능하고 "squid"처럼 string도 가능하기 때문입니다.

 

그런데 왜 에러가 날까요?

 

그건 바로 addNumbers 함수 내에서 number + string 타입의 덧셈이 일어나서입니다.

 

그래서 우리가 함수를 만들 때는 함수의 파라미터나 함수 리턴 값의 타입(type)을 정확히 지정해 줘야 합니다.

 

그래야만 코드를 짤 때 에러가 나타나고 쉽게 우리가 알아챌 수 있으니까요?

 

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

export default addNumbers;

다음과 같이 a, b에 number라는 타입을 지정해 줬습니다.

 

이제 functions-test.ts 파일을 볼까요?

 

이제야 VS Code 상에서 에러가 검출됩니다.

 

다행이네요.

 

이제 함수의 리턴 타입(type)에 대해 알아볼까요?

 

마우스를 addNumbers 이름 위에 살짝 갖다 대어 보세요.

 

function addNumbers(a: number, b: number): number

이라고 나타납니다.

 

마지막에 함수 파라미터 뒤에 : number라고 한 것이 바로 리턴 값의 타입(type)을 지정한 겁니다.

 

이 함수는 number 타입을 리턴한다고 명시적으로 지정한 거죠.

 

 

2. const 함수 명명

 

그러면 const로 함수를 만들 때는 어떻게 타입을 지정할까요?

 

export const addStrings = (str1: string, str2: string): string =>
   `${str1} ${str2}`;

이런 방식으로 합니다.

 

뭔가 어색하죠. 앞으로 애로우 함수를 많이 쓸 건데요. 이 방식의 타입 지정도 익숙해지셔야 합니다.

 

그럼 functions-test.ts 에서 addStrings 함수를 불러와 볼까요?

 

// functions-test.ts

import addNumbers, { addStrings } from "./functions";

console.log(addNumbers(1, 2));
// console.log(addNumbers(1, "squid"));

console.log(addStrings("squid", "game"));

 

addStrings 함수는 export default로 export 가 된 게 아니라서 디스트럭쳐 방식으로 import 해야 합니다.

 

import { addStrings } from "./functions";

 

export default 랑 같이 import 하려면 아까처럼 쓰시면 됩니다.

 

그리고 다음과 같이 addStrings 함수의 파라미터가 한 개가 생략되면 어떻게 될까요?

 

console.log(addStrings("player"));

 

"2개의 인수가 필요한데 1개를 가져왔습니다"라고 친절하게 설명해 주고 있습니다.

 

그럼 어떻게 처리할까요?  디폴트 파라미터를 쓰면 됩니다.

 

생략되면 함수 선언에서 제공한 디폴트 파라미터 값을 쓰는 거죠.

 

export const addStrings = (str1: string, str2: string = ""): string => 
  `${str1} ${str2}`;

 

str2: string = ""이라고 빈 공백 문자열을 넣어 줬습니다.

 

그럼 다시 아까 생겼던 에러가 없어집니다.

 

 

3. Union type in typescript

 

그러면 함수 파리 미터가 number 이거나 string 이거나 할 때는 어떻게 타입 지정을 할까요?

 

다음 예를 봅시다.

 

export const format = (title: string, param: string | number): string =>
    `${title} ${param}`

string | number이라고 | 표시를 두었습니다.

 

| 표시는 영어로 Or 표시인데요.

 

둘 중에 한 타입이라는 거죠.

 

정확한 용어로는 Union Type이라고 합니다.

 

그리고 다음과 같이 실행시킬 수 있습니다.

import addNumbers, { addStrings, format } from "./functions";

console.log(addNumbers(1, 2));

console.log(addStrings("squid", "game"));
console.log(addStrings("player"));
console.log(format("player", 456));

 

 

실행 결과도 잘 나타납니다.

 

 

 

4. No return type

 

함수에서 리턴 타입이 없을 때는 어떻게 하죠?

 

리턴할 게 없으면요.

 

export const printFormat = (title: string, param: string | number) =>
    console.log(`${title} ${param}`);

 

위 코드 위에 마우스를 올려 보겠습니다.

 

리턴 타입이 void라고 나옵니다.

 

맞습니다. 리턴이 없는 함수를 Void Function이라고 합니다.

 

그래서 다음과 같이 리턴 타입에 void를 지정하면 됩니다.

 

export const printFormat = (title: string, param: string | number): void =>
    console.log(`${title} ${param}`);

 

이제 Void 함수까지 알아보았는데요.

 

 

5. Promise 리턴

 

Promise를 리턴하는 함수는 어떻게 만들까요?

 

우리가 API를 이용해서 데이터를 fetch 할 때 많이 쓰이는데요.

 

export const fetchData = (url: string) =>
   Promise.resolve(`Data from ${url}`);

 

마우스를 올려볼까요?

 

VS Code는 Promise<string> 이라고 제네릭으로 표시하고 있습니다.

 

그럼 함수를 고쳐 볼까요?

 

export const fetchData = (url: string): Promise<string> =>
  Promise.resolve(`Data from ${url}`);

이제 에러 없다고 잘 나오는데요.

 

만약 여러분의 코드에서 Promise가 없다고 나오면 tsconfig.json 파일에서 다음과 같은 부분을 고쳐주시면 됩니다.

 

{
  "compilerOptions": {
              
    /* Language and Environment */
    "target": "es5",

저의 경우 target 부분이 es5라고 되어 있는데 혹시 여러분께서 에러 나신 다면 es5를 esnext로 바꾸시면 에러가 안 날 겁니다.

 

 

 

6. 함수 파라미터로 ... 스프레드 연산자가 올 경우

 

함수 파라미터로 가끔 스프레드 연산자(...)가 올 경우가 있는데요.

 

이럴 때는 어떻게 타입 지정하는지 알아볼까요?

 

// functions.ts

export function introduce(greet: string, ...names: string[]): string {
    return `${greet} ${names.join(" ")}`;
}

 

// functions-test.ts
console.log(introduce("Hello", "Player1", "Player2", "Player3"));

 

 

... names라고 해서 두 번째 파라미터부터 모든 파라미터를 가리킵니다.

 

names 타입은 string[]이고요.

 

실행 결과를 볼까요?

 

아주 잘 되고 있네요.

 

 

7. 컴파일 타임 에러 체킹

 

타입스크립트에 대한 가장 큰 오해는 타입스크립트는 런타임 경우에 에러를 체크하는 게 아니라 컴파일 타임 때 에러를 체크한다는 겁니다.

 

그래서 컴파일 타임 에러 체크가 중요한데요.

 

그래서 VS Code가 더욱 돋보이는 코드 에디터인 겁니다.

 

그럼 컴파일 타임과 런타임일 경우의 차이에 대해 알아보겠습니다.

 

// functions.ts

export function getName(user: { first: string; last: string; }): string {
    return `${user.first} ${user.last}`;
}

 

// functions-test.ts

import addNumbers, { addStrings, format, fetchData, introduce, getName } from "./functions";

console.log(getName({ first: "Sung", last: "Ki Hoon" }));

 

 

실행 결과도 아주 잘 나옵니다.

 

런타임 에러와 컴파일 타임 에러를 이해하기 위해 위의 모듈을 자바스크립트에서 불러와 볼까요?

 

js-function.js라는 자바스크립트 파일을 만들어 봅시다.

 

// js-functions.js

const { getName } = require("./functions");

console.log(getName({ first: "Sung", last: "Ki Hoon" }));

 

실행 결과를 볼까요?

 

모듈이 없다고 나옵니다.

 

왜 그런 걸까요?

 

js-functions.js를 node로 실행시켰습니다.

 

node로 실행시켰고 파일도 .js 확장자를 갖고 있는 자바스크립트 파일입니다.

 

그런데 js-function.js에서 require 방식으로 functions 모듈을 불러와야 되는데 없는 거죠.

 

왜 없는 걸까요?

 

functions.js 파일이 없는 겁니다.

 

그래서 npx tsx functions.ts 명령어로 타입스크립트를 컴파일해야 합니다.

 

아무 문제가 없네요.

 

그럼 다시 js-functions.js 파일을 실행해 볼까요?

 

 

이제 잘 작동됩니다.

 

그러면 코드를 약간만 변경해 볼까요?

 

// js-functions.js

const { getName } = require("./functions");

console.log(getName());

getName 함수 파라미터를 없앴습니다.

 

실행 결과는 어떻게 될까요?

 

undefined 에러가 나오고 난리입니다.

 

왜 그런 걸까요?

 

functions.ts 를 컴파일한 functions.js 파일에서 getName 모듈만 볼까요?

 

// functions.js  - 컴파일 후

function getName(user) {
    return user.first + " " + user.last;
}
exports.getName = getName;

 

되게 간단하게 자바스크립트 파일로 되어 있네요.

 

getName 파라미터인 user가 없으면 바로 에러가 나올 기세입니다.

 

즉, 런타임에서는 전혀 타입(type) 체킹(checking)을 하지 않는다는 얘기입니다.

 

그래서 타입스크립트는 컴파일 타임 에러 체킹이라고 하는데요.

 

런타임에서 이런 에러 체킹을 하면 전체적인 속도가 너무 느려집니다.

 

그러면 어떻게 컴파일 타임에서 이런 에러를 체크해야 할까요?

 

바로 옵셔널 사슬(Optional chaining)입니다.

 

// functions.ts

export function getName(user: { first: string; last: string; }): string {
    return `${user?.first} ${user?.last}`;
}

 

타입스크립트 파일입니다.

 

user.first 부분에서 user 뒤에 ? 를 넣었습니다.

 

이걸 옵셔널 체이닝(사슬)이라고 하는데요.

 

?가 붙으면 먼저 ?가 붙은 타입을 조사합니다.

 

만약 타입이 define 되었다면 점(.) 뒤에 있는 값을 참조하고 만약 타입이 define 되지 않았다면 점(.) 뒤의 값을 참조하지 않습니다.

 

되게 안정적인 타입 체킹인데요.

 

앞으로 React 코드를 작성할 때 많이 사용할 거라서 꼭 외워두셔야 합니다.

 

그러면 한번 더 컴파일해볼까요?

 

 

이제 에러 없이 undefined를 리턴하네요.

 

그럼 다시 컴파일된 자바스크립트 코드를 볼까요?

 

function getName(user) {
    return (user === null || user === void 0 ? void 0 : user.first) + " " + (user === null || user === void 0 ? void 0 : user.last);
}
exports.getName = getName;

 

 

옵셔널 체이닝을 넣었더니 컴파일된 자바스크립트 코드에서 user의 타입을 조사하기 시작했습니다.

 

이렇게 해야지만 컴파일 타임의 에러 체킹이 되는 거죠.

 

그러면 실행 결과의 undefined 값이 마음에 안 든다면 어떻게 해야 할까요?

 

바로 자바스크립트의 Nullish coalescing operator (Null 병합 연산자)를 이용해야 합니다.

 

널 병합 연산자는 ?? 인데요. 설명은 아래 MDN 문서를 참고 바랍니다.

 

 

 

그래서 우리의 functions.ts 코드를 어떻게 바꿔야 할까요?

 

 

export function getName(user: { first: string; last: string; }): string {
    return `${user?.first ?? "first"} ${user?.last ?? "second"}`;
}

위와 같이 널 병합 연산자 다음에 기본값을 넣어 줬습니다.

 

다시 자바스크립트로 컴파일해볼까요?

 

아주 잘 실행되었네요.

 

컴파일된 코드도 볼까요?

function getName(user) {
    var _a, _b;
    return ((_a = user === null || user === void 0 ? void 0 : user.first) !== null && _a !== void 0 ? _a : "first") + " " + ((_b = user === null || user === void 0 ? void 0 : user.last) !== null && _b !== void 0 ? _b : "second");
}
exports.getName = getName;

 

컴파일된 코드에도 기본값을 넣어주는 코드가 생겼네요.

 

 

이제 에러 체킹이 잘 되는지 코드를 다시 볼까요?

 

// js-functions.js

const { getName } = require("./functions");

console.log(getName());
console.log(getName({ first: "Sung" }));
console.log(getName({ first: "Sung", last: "Ki Hoon" }));

 

첫 번째 console.log에는 빈 파라미터, 두 번째는 한 개로 된 파라미터를 넣었습니다.

 

마지막으로는 제대로 된 파라미터를 넣었고요.

 

실행 결과를 볼까요?

 

 

 

의도한 데로 아주 잘 됐습니다.

 

지금까지 타입스크립트의 함수에 대해 알아보았는데요.

 

다음 시간에는 함수에 함수를 전달할 때 어떻게 하는지 알아보겠습니다.

 

 

그리드형