Frontend Study

[FE_Bootcamp] 74일차_TypeScript(1)

킹경후 2023. 5. 31. 17:28

무려무려 2주만에 돌아온 블로그!

그동안 솔로 프로젝트와 리팩토링 때문에 완전히 지칠대로 지쳐 블로그를 놓아버리고 말았다

미안해 블로그야~


1. TypeScript란?

TypeScript는 마이크로소프트에서 개발한 JavaScript의 상위 집합(Superset) 언어로, JavaScript에 정적타입 검사와 클래스 기반 객체 지향 프로그래밍 등의 기능을 추가된 구조이다.

 

JavaScript는 처음에는 브라우저에서만 동작하는 스크립팅 언어로 만들어졌지만 시간이 점점 흐르고, JavaScript로 웹 애플리케이션의 상호작용이 증가하면서, 웹 애플리케이션이 필요로 하는 JavaScript 코드의 양이 폭발적으로 늘어나게 되었다. 

 

JavaScript는 동적 타입이 결정되어 유연하고, 다양한 라이브러리와 프레임워크를 사용할 수 있는 장점이 있지만, 타입의 명시성이 부족하다는 단점이 있다.

이러한 단점을 보완하기 위해 등장한 것이 바로 TypeScript이다.


2. TypeScript를 사용하는 이유

console.log(1+1)
console.log(1+'1')
console.log('1'+'1')

 

위 코드의 결과는 어떻게 될까? 

첫 코드는 2라는 숫자가 나오지만, 아래 두 코드는 '11'이라는 문자열이 나온다.

 

JavaScript는 문자열과 숫자를 더할 경우, 숫자 타입의 인수 쪽을 강제적으로 타입 변환해 문자열을 만든다.

이렇게 JavaScript는 함수나 변수의 타입을 명시적으로 지정하지 않아도 동작하는 경우가 많아 예상치 못한 형변환이 일어나 원하는 결과를 얻기 힘들 때가 있다.

 

반면 TypeScript는 정적타입 검사 기능을 제공하며, 코드의 가독성과 유지 보수성을 높여준다.

이를 통해 개발자는 런타임 에러를 최소화하고, 코드 작성 시간을 단축하며, 협업 시 코드의 가독성을 높일 수 있다.

 

또한 TypeScript는 ES6의 문법을 포함한 최신 JavaScript 문법을 지원하여 JS와 완벽히 호환되고, 인터페이스(Interface), 제네릭(Generic), 데코레이터(Decorators) 등의 기능을 제공하여 객체 지향 프로그래밍을 보다 쉽게 할 수 있도록 도와준다.


3. TypeScript 환경 구성

npm init -y
npm install typescript --save-dev

 

터미널에 다음과 같은 명령어를 입력하면 TypeScript 개발 환경을 세팅할 수 있다.

이후 tsconfig.json이라는 파일을 만들고, 이곳에 다음과 같은 코드를 입력해준다.

 

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "sourceMap": true,
    "outDir": "./dist"
  },
  "include": [
    "src/**/*"
  ]
}

 

마지막으로 '파일명.ts'라는 파일을 만들어 TypeScript 문서를 작성하면 된다.


4. TypeScript의 타입

TypeScript의 변수는 기본적으로 '변수명:타입'의 형태를 가지고 있다.

변수를 생성할 때 미리 타입을 지정해 줌으로써, 원치않는 형변환을 막을 수 있다.

 

//Number
let num:number = 10

//String
let str:string = 'Hi!'

//Boolean
let bool:boolean = true

 

number, string, boolean 타입은 우리가 JS에서 사용하던 방식과 비슷하다. 다만 상기했듯 변수명 뒤에 ':타입'을 붙여 타입을 미리 설정해 주어야 한다.

 

//Array
let numArr:number[] = [1,2,3,4]
let strArr:Array<string> = ['Hi', 'Hello', 'Bye']

//Tuple
le tup:[string, number, boolean] = ['Song', 27, true]

 

배열은 만드는 방법이  2가지이다.

첫번째는 변수명 뒤에 ':타입[]'을 붙이는 방법, 두번째는 'Array<타입>'을 붙이는 방법.

어떤 방법을 사용해도 상관 없으나, 타입에 맞는 자료형들만 넣어주어야 한다.

numer 배열에는 정수만, string 배열에는 문자열만 들어가야 한다는 것이다.

 

튜플은 요소의 타입과 개수가 고정된 배열을 말한다. 

배열과는 다르게 모든 요소가 전부 같을 필요는 없지만, 배열의 index마다 타입이 정해져 있기 때문에 정확한 index에 접근해야 한다.

 

//Object
let obj: object = {
  name : 'Song',
  age : 27, 
  isAlive : true
};

//Better
let obj: {name : string, age : number, isAlive : boolean} = {
  name : 'Song',
  age : 27, 
  isAlive : true
};

 

Object는 JS에서와 같이 자료형이 아닌 타입을 나타낸다. 따라서 어떤 자료형이 들어가던 상관은 없다.

하지만 위 방법은 타입 안정성을 보장해주지 않기에, 아래 코드처럼 아예 프로퍼티 타입을 명시해주는 것이 좋다.

 

//Any
let unknown : any = 10

let known:number = 10

unknown = 20 //O
known = 20 //X

let allArr : any = [1, 'hi', true, [1,2,3]]

 

any 타입은 타입 검사를 하지 않고자 할 때 사용한다. any 타입을 사용하게 되면, 변수에 값을 재할당하는 경우 타입을 명시한 변수와 달리 타입에 구애받지 않고 값을 재할당할 수 있다.

 

또한 any 타입은 여러 타입이 섞인 배열 등 '타입의 일부만 알고, 전체는 알지 못할 때' 유용하게 쓸 수 있다


5. TypeScript의 함수

TypeScript에서 함수를 표현할 때는 매개변수의 타입과 반환 타입을 명시해야 한다.

 

const add = (x:number, y:number):number => {
  return x + y
}

 

각 매개변수에 해당하는 타입을 작성한 뒤, 반환되는 타입도 괄호 뒤에 작성을 해줘야 한다.

만약 리턴값이 없다면 반환 타입 자리에 'void'를 써주면 된다. C언어를 생각하면 될 것 같다.

 

add(1) //X
add(1, 2) //O
add(1,2,3) //X

 

하지만 JavaScript와 다르게, TypeScript는 설정한 매개변수와 전달인자의 개수가 같아야 한다.

add 함수는 2개의 매개변수를 가지고 있는데, 1개 또는 3개의 전달인자를 전달할 경우 오류가 발생하게 된다.

 

const add = (x:number, y?:number):number => {
  return x + y
}

add(1) //O -> return 1
add(1, null) -> //O -> return 1
add(1,2) //O -> return 3
add(1,2,3) //X

 

따라서, 전달인자에 null 또는 undefined라도 전달을 해주거나, 매개변수 뒤에 ?를 붙여 매개변수를 선택적으로 사용해 주어야 한다.


6. Union Type과 Intersection Type

A. Union Type

유니온 타입은 둘 이상의 타입을 합쳐서 만들어진 새로운 타입이다. '|' 연산자를 이용하며, 자바스크립트의 '||' 연산자와 비슷한 역할을 한다. 예를 들어, number | string은 숫자 또는 문자열이라는 의미이다.

 

const union = (value : string|number) : void =>{
  if(typeof value === 'number'){
    console.log(`${value}는 정수`)
  } else if (typeof value === 'string'){
    console.log(`${value}는 문자열`)
  }
}

union(1) // 1는 정수
union('안녕') //안녕는 문자열

 

하지만 유니온 타입인 값이 있으면, 유니온에 있는 모든 타입에 공통인 멤버들에만 접근할 수 있다.

 

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

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

const askSomeone = (someone: Developer | Person) : void => {
	console.log(someone.name);
}

 

위 경우, someone은 Developer와 Person의 공통 프로퍼티인 name에만 접근할 수 있다.

만약 skill이나 age같은 공통되지 않은 프로퍼티에 접근하려면 '타입 가드'를 사용해야 한다.

 

const askSomeone = (someone: Developer | Person) : void => {
  if ('skill' in someone) {
    console.log(someone.skill);
  }

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

 

in 연산자는 객체의 속성 이름과 함께 사용하여 해당 속성이 객체 내에 존재하는지 여부를 검사해주는 연산자이다.


B. Intersection Type

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

 

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

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

const askSomeone = (someone: Developer & Person) : void => {
	console.log(someone.name);
	console.log(someone.skill);
	console.log(someone.age);
}

 

& 연산자로 Intersection Type을 만들면 여러 타입을 하나의 타입으로 만들어 줄 수 있다.

따라서, 타입 가드를 쓰지 않아도 쉽게 공통되지 않은 프로퍼티에 접근할 수 있게 된다.