[TypeScript] #01. Types

ZenTechie·2023년 5월 18일
0

TypeScript

목록 보기
1/3
post-thumbnail

타입 선언 방식

Implicit Types vs Explicit Types

TS의 Type 시스템은 다음과 같이 구성된다.

  1. 명시적 정의하기(Explicit)
  2. 변수만 생성하기(Implicit)

명시적 정의하기

명시적 정의는 변수의 타입을 변수를 생성할 때 정의하는 것이다.

let a : boolean = false
let b : number = 26

보다시피 booleannumber라는 타입을 정의함으로써 그에 맞는 값(false, 26)을 할당했다.

만약, 각 타입에 맞지 않는 값을 선언하면 어떻게 될까?

let a : boolean = "How's it work?"

string 타입을 boolean 타입에 할당할 수 없다는 에러가 발생한다.

여기서 타입 확인은 TS의 Type Checker가 수행한다.

변수만 생성하기

타입을 정의하지 않고 변수만 생성하는 방식도 있다.

let a = false
let b = 26

우리가 타입을 정의하지 않는다면 TS 내부적으로 해당 변수의 타입을 추론한다.
즉, 여기서 a는 boolean, b는 number로 내부적으로 타입을 추론하는 것이다.

이때 값을 기존 타입과 다르게 변경하면 어떻게 될까?

a = 1

Type 'number' is not assignable to type 'boolean'. 라는 오류가 발생한다.

위에서 말했듯이 number 타입을 boolean 타입에 할당할 수 없다는 것이다.

타입 종류

primitive 타입

primitive 타입은 원시적 타입이다. number, string, boolean이 이에 속한다.
타입 선언은 위에서 봤듯이 굉장히 단순하다. JavaC를 배웠던 사람이면 익숙할 것이다.

let a : number = 1
let b : string = "s"
let b : boolean = false

숫자 : number
문자열 : string
논리 : boolean

optional 타입

예를 들어, player라는 objectname 프로퍼티를 갖고있다고 가정하자.

const player = {
  name: "lunarmoon",
}

이때 player들 중 몇몇은 age 프로퍼티가 있을수도 있고 없을수도 있고 name은 항상 가진다고 가정하자.
일단, name 프로퍼티를 바꿔보자.

const player : object = {
  name: "lunarmoon",
}

player.name = "john"

위와 같이 object라는 타입을 명시해서 작성하게되면 object 타입에 name 프로퍼티가 없다고 뜰 것이다.
자명하게도 내부적으로 object 타입에는 name이라는 프로퍼티는 존재하지 않는다!
(✅ nameplayer라는 object에 존재하는 것이다.)

따라서, 우리는 이를 아래와 같이 바꿔야 한다.

const player : {
	name : string,
	age : number
} = {
  name: "lunarmoon",
}

위에서 몇몇의 playerage가지지 않을 수 있다고 말했다. 그래서 위와 같이 age의 타입은 명시해주고 값은 전해주지 않았는데 오류가 발생한다.

TS에서 특정 프로퍼티가 값을 가지지 않을수도 있게 하고 싶으면, 단순히 값을 전해주지 않기만 하면 되는게 아니다.

다음과 같이 ?를 사용해서 표현해야 한다.

const player : {
	name : string,
	age ?: number
} = {
  name: "lunarmoon",
}

이렇게 되면 age의 타입은 number | undefined 로 표시가 된다.
(아마 Kotlin을 배웠으면 ?가 익숙할 수 있다. ?.를 자주 사용하기 때문에..)

자 여기서 if문을 사용해서 playerage조작하고 싶다.

if (player.age < 10) { ... }

이는 틀린 코드이다. 왜 일까? 🧐
앞서 ?: 로 타입을 선언하게 되면 undefined 타입같이 할당된다고 했다.

undefined비교 자체를 할 수가 없기 때문에,
먼저 해당 타입이 존재하는지 안하는지 확인해야 한다.
그래서, 아래와 같이 작성해야한다.

if (player.age && player.age < 10) { ... }

Alias(별칭) 타입

자 만약에 같은 구조의 object인데 변수의 이름만 다르게 하고 싶을 땐 어떻게 할 수 있을까?
단순하게 변수 이름만 다른 똑같은 object를 하나 만들면 된다.

const player2 : {
  name: string,
  age ?: number,
} = {
  name: "john",
  age : 18
}

이렇게 만들면 너무 비효율적이다. 새로운 object를 만들고자 할 때 마다 똑같은 프로퍼티를 또 작성해야 한다. 손목만 아플 뿐..

이때 사용하는 것이 Alias 타입, 즉 별칭 타입이다.
이는 프로퍼티들을 하나의 타입으로 뽑아내는 방식이다.

다음과 같이 만들 수 있다.

type Player = {
  name : string,
  age ?: number,
}

const player : Player = {
  name : "lunarmoon"
}

const player2 : Player = {
  name : "john"
  age: 18
}

확실히 동일한 코드를 하나의 별칭 타입으로 빼놓으니, 코드가 간결해보인다.
이렇게 되면 코드가 간결해질 뿐만 아니라 코드의 재사용성이 높아진다.

추가로 이 별칭 타입은 object만 유효한게 아니다. 어느 타입에서든 적용할 수 있다.
여기서는 nameage에 별칭 타입을 적용해보자.

type Age = number
type Name = string

그러면 Player 타입을 다음과 같이 바꿀 수 있다.

type Player = {
  name : Name,
  age : Age
}

이렇게 별칭 타입을 적용하면, 해당 프로퍼티가 어떤 의미를 갖는지 한눈에 보기 좋아진다.

함수의 return 타입

이번에는 함수return 타입을 어떻게 정의할 수 있는지 살펴보자.
함수도 변수와 마찬가지로 같은 방식으로 타입을 정의한다.

예를 들어, string 타입인 name을 인자로 받으면 name을 가지는, 즉 위에서 만들었던 Player 타입인 객체를 return 하는 함수를 만들어보자.

function playerMaker(name : string) : Player {
  return {
    name // == name : name
  }

함수를 선언할 때 마지막: [Type] 만 붙여주면 된다.

추가로, 화살표 함수는 어떻게 return 타입을 정의할 수 있을까?

const playerMaker = (name : string) : Player => ({name})

위와 같이 작성하면 된다.
아마 JS에서는 다음과 같이 만들었을 것이다.

const playerMaker = (name : string) => ({name})

보다시피 : [Type] 만 추가된것을 알 수 있다.

Tuple

Tuplearray생성할 수 있게 하고 정해진 길이(개수)와 순서가 존재하며 특정 위치에 특정 타입이 존재해야 한다.

Tuple을 한번 만들어보자.

const player : [string, number, boolean] = []

여기서 타입만 선언하고 값을 넣지는 않았는데, 이렇게 되면 에러가 발생한다.
따라서, 값을 꼭 할당해줘야 한다.

const player : [string, number, boolean] = ["lunarmoon", 26, true]

다음과 같이 [] 안에 길이(개수)를 정해줘야 한다.
이때, Tuple서로 다른 타입들이 하나의 배열에 존재할 수 있다.

Tuple을 왜, 언제 써야하는지 와닿지 않을 수 있다.

그냥 Tuple을 사용하면 항상 정해진 개수의 요소를 가져야 하는 array를 생성할 수 있다는 것만 기억해두자.

추가로, 특정 위치를 접근하는 것을 같이 살펴보자.

player[0] = 1

해당 접근은 에러가 발생한다. 왜냐하면, player[0]string 타입인데 number 값을 할당하려고 했기 때문이다. 그리고 readonly 속성도 Tuple에 추가할 수 있다.

기타 타입(undefined, null, any)

다음 타입들은 JS에도 존재하는 타입들이다.

any

any는 말 그대로 anything을 의미한다. anyTS로부터 빠져나오고 싶을 때 쓰는 타입이다.
즉, TS의 보호장치들로부터 빠져나오고 싶을 때 사용하는 타입이다.

우리가 아무런 타입을 선언하지 않는다면 기본적으로 any 타입을 갖게된다.

let a = []

위의 a라는 배열은 any 타입을 갖게 된다.

undefined

undefined선언도 하지 않고 할당도 하지 않는 타입이다.
말 그대로 정의되지 않음을 의미한다.

null

null선언은 하되 할당을 하지 않는 타입이다.

any : 아무 타입
undefined : 선언 ❌ 할당 ❌
null : 선언 ⭕️ 할당 ❌

TS에만 존재하는 타입(void, unknown, never)

void

void아무것도 return하지 않는 함수대상으로 사용한다.
함수 내에 return 문없다면 TS가 내부적으로 유추한다.

function say() {
  console.log("HI!")
}
// "HI!", say() : void

unknown

만약 우리가 API로부터 응답을 받는데, 그 응답의 타입이 뭔지 모른다고 가정해보자.
이때 사용하는 것이 unknown 타입이다.

unknown으로 변수를 선언하고 해당 변수를 가지고 어떤 작업을 하려고 한다면, TS는 이 변수의 타입을 먼저 확인해야 하는 방식으로 일종의 보호장치를 제공한다.

아래의 코드를 살펴보자.

let a : unknown
let b = a + 1

해당 코드는 a is of type 'unknown' 이라는 에러가 발생한다.

따라서 우리는 아래와 같이 먼저 해당 변수의 타입을 먼저 확인해야 한다.

if (typeof a === 'number') {
  let b = a + 1
}
if (typeof a === 'string') {
  let b = a.toUpperCase()
}

✅ 결론적으로, unknown변수의 타입을 미리 알지 못할 때 사용한다.

never

never는 많이 사용하지 않지만 알아두면 좋다.
never절대 실행되지 않음을 의미한다.

아래 코드는 "HI!"return하는 never 타입의 함수이다.

function say() : never {
  return "HI!"
}

위 코드는 'string을 never에 할당할 수 없다'에러를 발생시킨다.

never는 함수가 예외를 throw 하거나 프로그램 실행을 종료할 때, 그리고 절대 return 하지 않을 때 사용한다.

function say() : never {
  throw new Error("Error")
}

readonly 속성

readonly는 말 그대로 읽기 전용을 의미한다.
JS에서는 기본적으로 이런 동작이 없는데, TS에서는 지원한다.

자, 위에서 만들었던 Playername 프로퍼티에 readonly 속성을 추가해보자.
그리고 name의 값을 변경해보자.

type Player = {
  readonly name : Name,
  age : Age
}

const player : Player = {
  name: "lunarmoon",
  age: 26
}

player.name = "john"

위 코드에서 name값을 할당할 수 없다는 에러가 뜬다.
즉, readonly'읽기 전용'을 의미하고 한번 값을 할당하면 값을 변경하지 못하게 하는 기능을 한다.

배열에도 한번 적용해보자.

const numbers : readonly number[] = [1,2,3,4,5,6,7]
numbers.push(1)

당연하게도 push할 수 없다는 에러가 뜬다.

profile
데브코스 진행 중.. ~ 2024.03

0개의 댓글