CodeStates/TypeScript

Section4 / Unit5 : TypeScript(타입, 함수)

yeeendy 2023. 7. 26. 17:26

TypeScript

JavaScript의 상위 집합(Superset) 언어

→ JavaScript에 정적타입 검사와 클래스 기반 객체 지향 프로그래밍 등의 기능을 추가

→ JavaScript가 발전하면서 생긴 단점을 보완하기 위해 등장

 

등장 배경

JavaScript는 초반, 브라우저에서만 동작하는 스크립팅 언어였다.

JS로 웹 애플리케이션의 상호작용이 증가하면서 코드의 양이 폭발적으로 늘어나게 되면서, 한계가 부각되었다.

JS 장점 : 동적 타입이 결정되어 유연하고, 다양한 라이브러리와 프레임워크 사용 가능

JS 단점 : 타입의 명시성이 부족

→ ex) let add = ( x, y ) -> { return x + y;}    =>    add(5, "7")의 값으로 "57"이 나온다

→ 숫자 타입의 인수 쪽을 강제적으로 타입 변환해 문자열을 만든다.

 

이런 문제점을 보완하기 위해 TypeScript 언어가 등장!

 

TypeScript 장점

  • 정적타입 검사 기능 제공
  • 코드의 가독성
  • 유지 보수성 ↑
  • 런타임 에러 최소화
  • 코드 작성 시간을 단축

TypeScript 타입

  • Boolean(불리언)
    • 가장 기본적은 데이터 타입
    • 참(true), 거짓(false) 값
let isShow: boolean = true;
let isDone: boolean = false;
  • Number(숫자)
    • 정수와 실수의 구분 없음
    • 이 외에도 추가로 bigint를 지원
let number1: number = 5;
let number2: number = 0.7;
  • String(문자열)
    • 큰따옴표(")나 작은따옴표(')를 사용하여 문자열 데이터를 표현
    • 백틱(`)을 사용한 문자열인 템플릿 리터럴을 사용하면
      여러 줄에 걸쳐 문자열을 작성 가능
let firstName: string = "coding";
let lastName: string = 'kim';
let longString: string = `Kimcoding is a developer.
He is 20 years old.`
  • Array(배열)
    • 두 가지 방법으로 배열 타입 선언
      1. []
      2. 제네릭 배열
    • 기본적으로 하나의 타입만 작성하게 되어 있음
    • 타입을 혼용해서 작성하는 것은 불가능
//첫 번째 방법
let items: string[] = ["apple", "banana", "grape"];

//두 번째 방법
let numberList: Array<number> = [4, 7, 100];
  • Tuple(튜플)
    • 요소의 타입과 개수가 고정된 배열을 표현 가능
    • 모든 요소가 전부 같을 필요는 없음
      → 배열의 인덱스마다 타입이 정해져 있기 때문에 정확한 인덱스에 접근할 필요가 있음
let user: [string, number, boolean] = ["kimcoding", 20, true];
  • Object(객체)
    • 원시 타입이 아닌 타입
    • TypeScript에서 object 타입은 모든 객체를 수용하는 타입으로,
      객체의 프로퍼티 타입들이 any로 지정되기 때문에 어떠한 프로퍼티라도 추가할 수 있음
      → 하지만 타입 안정성을 보장하지 않기 때문에 추천 X
    • 객체의 프로퍼티 타입들을 각기 명시해 주는 것이 좋음
    • key-value에 구체적인 타입까지도 지정 가능
let obj: object = {};
let user: {name: string, age: number} = {
	name: "kimcoding",
	age: 20
}
  • Any
    • 타입 검사를 하지 않고자 할 때 any 타입을 사용
      ex) 클라이언트에서 유저로부터 받은 데이터 및 서드파티 라이브러리에서 들어오는 값인 경우
      개발자가 알지 못하는 타입일 수 있음
    • 변수에 값을 재할당하는 경우 타입을 명시한 변수와 달리 타입에 구애받지 않고 재할당 가능
    • 엄격한 타입 검사를 진행하지 않기 때문에, 실제 할당된 값이 가지지 않는 메서드 및 프로퍼티로 접근해도 에러나지 않음
    • 실제 할당된 값이 가지지 않는 메서드 및 프로퍼티이기 때문에 반환되는 값은 undefined
    • any 타입은 타입의 일부만 알고, 전체는 알지 못할 때 유용
      → 여러 타입이 섞인 배열을 받고자 할 때 유용함
let maybe: any = 4;
let obj: object = {};

//에러가 납니다.
obj = "hello";

let maybe: any = 4;

//정상적으로 동작합니다.
maybe = true;
let maybe: any = 4;

//undefined로 출력됩니다.
console.log(maybe.length);
let list: any[] = [1, true, "free"];

//any로 다루고 있기 때문에 index 1번째 요소가 boolean 타입이지만 number 타입으로 재할당할 수 있습니다. 
list[1] = 100;

TypeScript의 함수

  • 기명 함수(named function)
  • 화살표 함수(arrow function)
//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에서 함수를 표현할 때는 매개변수의 타입과 리턴값의 타입을 명시해야 함
  • 반환되는 타입은 타입추론을 이용하여 생략할 수도 있음
    • 타입 추론 : 리턴값을 작성하지 않아도 TypeScript 컴파일이 스스로 판단해서 타입을 넣어줌
//named function
function add(x: number, y: number) {
	return x + y;
}

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

  • 함수에 리턴값이 없다면, void를 사용하여 작성
let printAnswer = (): void => {
	console.log("YES");
}
  • 매개변수의 개수에 맞춰 전달인자를 전달해야함
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');
  • 선택적 매개변수를 원한다면 매개변수의 이름 끝에 물음표(?)를 붙임으로써 해결
let greeting = (firstName: string, lastName?: string): string => {
	return `hello, ${firstName} ${lastName}`;
}

//정상적으로 작동합니다.
greeting('coding');
//전달인자를 하나만 전달했기 때문에, 뒤의 매개변수는 undefined로 반환

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

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

유니온(Union) 타입

둘 이상의 타입을 합쳐서 만들어진 새로운 타입

  • | 연산자를 이용하며, 자바스크립트의 || (OR) 연산자와 같이 "A이거나 B이다"라는 의미의 타입
    ex) number | string 은 숫자 또는 문자열 타입을 의미
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

유니온(Union) 타입의 장점

  • 타입을 추론할 수 있기 때문에, 타입에 관련된 API를 쉽게 자동완성으로 얻어낼 수 있음
    → any 타입은 자동완성 기능 사용하기 어려움
  • 코드의 가독성 ↑

유니온(Union) 타입의 유의점

  • 유니온에 있는 모든 타입에 공통인 멤버들에만 접근할 수 있음
interface Developer {
  name: string;
  skill: string;
}

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

위에 예시에서는 askSomeone 함수 내부에서는 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);
  }
}
//in연산자 : 객체의 프로퍼티 이름과 함께 사용되며, 해당 프로퍼티가 객체 내에 존재하는지 여부를 검사

 

인터섹션(Intersection) 타입

둘 이상의 타입을 결합하여 새로운 타입을 만드는 방법

&연산자를 사용하여 표현

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

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

type User = Developer & Person;

User 변수는 Developer, Person 각각에 정의된 속성 모두를 받게 됨

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});