코드스테이츠-부트캠프 [TypeScript] 기초(2)

김희목·2024년 3월 31일
0

코드스테이츠

목록 보기
52/56

TypeScript의 함수

JavaScript에서 함수는 모든 애플리케이션의 기본적인 구성 요소입니다. JavaScript에서의 함수와 마찬가지로 TypeScript에도 함수는 JavaScript와 마찬가지로 기명 함수(named function)와 화살표 함수(arrow function) 등으로 만들 수 있습니다.

JavaScript에서 함수를 이렇게 작성했습니다.

//named function
function add(x, y){
	return x + y;
}

//arrow function
let add = (x, y) => {
	return x + y;
}

이를 TypeScript로 다시 표현해 보겠습니다.

//named function
function add(x: number, y: number):number {
	return x + y;
}

//arrow function
let add = (x: number, y: number): number => {
	return x + y;
}

TypeScript에서 함수를 표현할 때는 매개변수의 타입과 리턴값의 타입을 명시해야 합니다. 각 매개변수에 해당하는 타입을 작성하고, 리턴값의 타입을 괄호 뒤에 작성을 하면 됩니다. 반환되는 타입은 타입추론을 이용하여 생략할 수도 있습니다.

//named function
function add(x: number, y: number) {
	return x + y;
}

//arrow function
let add = (x: number, y: number) => {
	return x + y;
}

이렇게 리턴값을 작성하지 않아도 TypeScript 컴파일이 스스로 판단해서 타입을 넣어주며, 이를 타입 추론이라고 부릅니다.

그리고 만약 함수에 리턴값이 없다면, void를 사용하여 작성할 수 있습니다.

let printAnswer = (): void => {
	console.log("YES");
    }

또한 TypeScript는 JavaScript와 달리 매개변수의 개수에 맞춰 전달인자를 전달해야 합니다.

let greeting = (firstName: string, lastName: string): string => {
	return `hello, ${firstName} ${lastName}`;
}

//에러가 납니다.
greeting('coding');

//정상적으로 작동합니다.
greeting('coding', 'kim');

//너무 많은 매개변수를 보내 에러가 납니다.
greeting('coding', 'kim', 'hacker');

만약 개발자가 전달인자를 전달하지 않거나, undefined를 전달했을 때 할당될 매개변수의 값을 정해놓을 수도 있습니다. 이는 JavaScript에서의 default parameter와 같은 동작을 합니다.

let greeting = (firstName: string, lastName: string ="kim"): string => {
	return `hello, ${firstName} ${lastName}`;
}

//정상적으로 작동합니다. 
greeting('coding');

//정상적으로 작동합니다.
greeting('coding', undefined);

//너무 많은 매개변수를 보내 에러가 납니다.
greeting('coding', 'kim', 'hacker');

이때는 뒤의 인자로 undefined를 보내도 값은 “hello, coding kim”으로 반환됩니다.

혹은 선택적 매개변수를 원한다면 매개변수의 이름 끝에 물음표(?)를 붙임으로써 해결할 수도 있습니다.

let greeting = (firstName: string, lastName?: string): string => {
	return `hello, ${firstName} ${lastName}`;
}

//정상적으로 작동합니다.
greeting('coding');

//정상적으로 작동합니다.
greeting('coding', 'kim');

//너무 많은 매개변수를 보내 에러가 납니다.
greeting('coding', 'kim', 'hacker');

그러나 이때는 greating('coding')과 같이 전달인자를 하나만 전달했기 때문에, 뒤의 매개변수는 undefined로 반환이 됩니다.


실습 - 함수

/* 1-1번 */
function sumNumber(a:number, b:number) {
  return a + b;
}

/* 1-2번 */
const sumNumber2 = (a:number, b:number) => {
  return a + b;
};

/* 2번 */
let sumString = (first:string, last:string) => {
  return `${first} ${last}`;
};

console.log(sumString('hi', 'codestates'));

/* 3번 */
let sumString2 = (first:string, last?:string) => {
  return `${first} ${last}`;
};

//아래 코드도 동작하도록 구현해봅시다.
console.log(sumString2('hi'));

/* 4번 */
let printError = (): void => {
  console.log('error message');
};

유니온(Union) 타입

TypeScript는 연산자를 이용해 타입을 정할 수 있습니다. JavaScript에서도 보았던 ||(OR) 연산자나 && (AND)와 같은 연산자를 이용하여 만들 수 있습니다. | 연산자를 이용한 타입을 유니온(Union) 타입이라고 하며, & 연산자를 이용한 타입은 인터섹션(Intersection) 타입이라고 부릅니다.

유니온 타입은 둘 이상의 타입을 합쳐서 만들어진 새로운 타입입니다. | 연산자를 이용하며, 자바스크립트의 || (OR) 연산자와 같이 “A이거나 B이다”라는 의미의 타입입니다. 예를 들어, number | string은 숫자 또는 문자열 타입을 의미합니다. 아래의 코드를 하나 보겠습니다.

function printValue(value: any): void {
  if (typeof value === "number") {
    console.log(`The value is a number: ${value}`);
  } else {
    console.log(`The value is a string: ${value}`);
  }
}

printValue(10); // The value is a number: 10
printValue("hello"); // The value is a string: hello

위의 코드는 value 매개변수의 타입을 any로 정의하고, 타입이 number인지 string인지에 따라 if-else 문으로 나누어 출력하고 있습니다. 그러나 any를 사용하는 것은 JavaScript로 작성하는 것과 큰 차이가 없기 때문에, 유니온 타입을 사용해 TypeScript의 이점을 살리면서 코딩하는 것이 좋습니다.

function printValue(value: number|string): void {
  if (typeof value === "number") {
    console.log(`The value is a number: ${value}`);
  } else {
    console.log(`The value is a string: ${value}`);
  }
}

printValue(10); // The value is a number: 10
printValue("hello"); // The value is a string: hello

위의 printValue 함수는 숫자 또는 문자열 값을 입력받고 있습니다. 이때, 유니온 타입을 사용해 number | string 타입으로 지정하고 있습니다. 이후 입력된 값의 타입을 typeof 연산자를 사용하여 검사한 후, 해당 값이 숫자인 경우와 문자열인 경우 각각 다른 로그를 출력합니다. 이처럼 유니온 타입은 다양한 타입의 값을 처리해야 하는 경우 유용합니다.


유니온(Union) 타입의 장점

유니온 타입을 사용하면 타입을 추론할 수 있기 때문에, 타입에 관련된 API를 쉽게 자동완성으로 얻어낼 수 있습니다.

그러나 any 타입을 사용하면 타입을 추론할 수 없어, 자동완성 기능을 사용하기가 어렵습니다.

또한 코드의 가독성을 높일 수 있습니다.

let value: string | number | boolean;

이렇게 string | number | boolean 타입으로 선언된 변수는 문자열, 숫자, 불리언 타입 중 하나의 값을 가질 수 있다는 것이 명시적으로 표시되어 코드를 이해하기 쉽게 만들어 줍니다.


유니온(Union) 타입 사용 시 유의할 점

유니온 타입인 값이 있으면, 유니온에 있는 모든 타입에 공통인 멤버들에만 접근할 수 있기 때문에 유의해야 합니다.

interface Developer {
  name: string;
  skill: string;
}

interface Person {
  name: string;
  age: number;
}

이렇게 인터페이스를 사용하여 Developer와 Person을 정의했습니다.

function askSomeone(someone: Developer | Person) {
	console.log(someone.name);
}

그러나 실질적으로 askSomenone 함수 내부에서는 Developer와 Person이 갖고 있는 공통 프로퍼티인 name에만 접근할 수 있습니다. 왜냐하면 공통되고 보장된 프로퍼티만 제공해야 하기 때문입니다. 만약 나머지 프로퍼티에도 접근하고 싶다면 타입 가드를 사용해야 합니다.

타입 가드(Type Guard)란? TypeScript에서 타입을 보호하기 위해 사용되는 기능 중 하나입니다. 타입 가드는 특정 코드 블록에서 타입의 범위를 제한해 해당 코드 블록 안에서 타입 안정성을 보장해 줍니다.

아래 코드는 타입 가드를 사용해 작성된 코드입니다.

function askSomeone(someone: Developer | Person) {
  // in 연산자 : 타입스크립트에서 객체의 속성이 존재하는지를 체크하는 연산자
  // in 연산자는 객체의 속성 이름과 함께 사용하여 해당 속성이 객체 내에 존재하는지 여부를 검사
  if ('skill' in someone) {
    console.log(someone.skill);
  }

  if ('age' in someone) {
    console.log(someone.age);
  }
}

TypeScript에서는 in 연산자를 제공하고 있습니다. in 연산자는 객체의 프로퍼티 이름과 함께 사용되며, 해당 프로퍼티가 객체 내에 존재하는지 여부를 검사합니다.


인터섹션(Intersection) 타입

인터섹션(Intersection)은 둘 이상의 타입을 결합하여 새로운 타입을 만드는 방법입니다. & 연산자를 사용하여 표현합니다.

interface Developer {
  name: string;
  skill: string;
}

interface Person {
  name: string;
  age: number;
}

type User = Developer & Person;

이런 식으로 타입을 결합해 사용할 수 있습니다. 여기서 User 변수는 Developer, Person 각각에 정의된 속성 모두를 받게 됩니다. 여기서 type 은 추후 나올 타입 별칭에서 배우게 되므로, 지금은 인터섹션 타입을 어떻게 사용하는 지에 대해서만 집중하겠습니다.

이런 식으로 인터섹션으로 타입을 연결해 하나의 단일 타입으로 표현할 수 있기 때문에, 타입 가드가 필요하지 않습니다.

interface Developer {
  name: string;
  skill: string;
}

interface Person {
  name: string;
  age: number;
}

function askSomeone(someone: Developer & Person) {
  console.log(someone.age);
	console.log(someone.name);
	console.log(someone.skill);
}

위의 코드는 인터섹션 타입을 사용하여 Developer와 Person을 하나의 타입으로 묶었습니다. 따라서 askSomeone 함수 내에선 정의된 프로퍼티에 전부 접근할 수 있습니다.

그러나 인터섹션 타입은 타입 가드는 필요 없는 반면 Developer와 Person이라는 새로운 교집합을 만들어 내는 것이기 때문에, 전달인자를 전달할 때 모든 프로퍼티를 전부 보내줘야만 합니다. 반대로 유니온 타입은 타입 가드를 해줘야 하지만 전달인자를 전달할 때 선택지가 생기게 됩니다.

interface Developer {
  name: string;
  skill: string;
}

interface Person {
  name: string;
  age: number;
}

function askSomeone(someone: Developer | Person) {
	//이런 식으로 프로퍼티에 접근할 수 있습니다.
  if ('skill' in someone) {
    console.log(someone.skill);
  }

  if ('age' in someone) {
    console.log(someone.age);
  }
}

//유니온 타입은 전달인자를 전달할 때 선택지가 생깁니다.
askSomeone({name: '김코딩', skill: '웹 개발'});
askSomeone({name: '김코딩', age: 20});

function askSomeone2(someone: Developer & Person) {
	//타입 가드를 사용하지 않아도 모든 프로퍼티에 접근할 수 있습니다.
  console.log(someone.age);
	console.log(someone.name);
	console.log(someone.skill);
}

//그러나 인터섹션 타입으로 결합하게 된다면 전달인자를 전달할 때 선택지가 없습니다.
askSomeone2({name: '김코딩', skill: '웹 개발', age:20});

실습 - 유니온,인터섹션 타입

유니온(Union) 타입

interface KimCoding {
  name: string;
  age: number;
}

interface ParHacker {
  name: string;
  age: string;
}

function printAge(person: KimCoding | ParHacker) {
  let age: string | undefined;

  if (typeof person.age === 'number' || typeof person.age === 'string') {
    age = person.age.toString();
  }

  console.log(`${person.name}의 나이는 ${age}살 입니다.`);
}

const kimcoding: KimCoding = {
  name: '김코딩',
  age: 30,
};

const parhacker: ParHacker = {
  name: '박해커',
  age: '서른',
};

printAge(kimcoding);
printAge(parhacker);

인터섹션(Intersection) 타입

interface Admin {
  isAdmin: true;
}

interface NotAdmin {
  isAdmin: false;
}

type User = {
  name: string;
  email: string;
} & (Admin | NotAdmin);

function sendEmail(user: User) {
  console.log(`안녕하세요, ${user.name}!`);
  if (user.isAdmin) {
    console.log(
      `
      권한이 admin이시군요.
      이메일은 ${user.email} 입니다.
      `
    );
  } else {
    console.log(
      `
      권한이 user이시군요.
      이메일은 ${user.email} 입니다.
      `
    );
  }
}

const kimcoding: User = {
  name: '김코딩',
  email: 'kimcoding@codestates.com',
  isAdmin: false,
};

const parkhacker: User = {
  name: '박해커',
  email: 'parkhacker@codestates.com',
  isAdmin: true,
};

sendEmail(kimcoding);
sendEmail(parkhacker);

0개의 댓글