타입스크립트 기본 타입(2)

김동현·2022년 10월 28일
0

TypeScript

목록 보기
4/18
post-thumbnail

object

object 타입의 포함 관계는 아래와 같습니다.

포함 관계 : unkwown, any > object > Function, Array, Class, Tuple,, > any

객체의 타입 경우 object 혹은 객체 리터럴 형태인 { key: type; ,,, } 형식으로 사용할 수 있습니다.

객체 리터럴 형태 객체 타입

프로퍼티 타입

위 그림처럼 타입이 객체 리터럴 형식처럼 객체 타입이 명시되어 있고, "프로퍼티 키 값"과 "프로퍼티 값의 타입" 그리고 세미콜론(혹은 콤마)으로 각 프로퍼티를 구분하고 있습니다.

즉, 객체 리터럴 형태로 프로퍼티 타입을 명시할 때 ": { 프로퍼티이름: type; }" 형식을 사용합니다.

메서드 타입

프로퍼티 이외 객체의 메서드에 대한 구조도 정의할 수 있습니다.

메서드의 구조를 작성할 때는 메서드의 이름, 메서드의 매개변수 개수와 타입, 반환값의 타입을 모두 작성해주어야 합니다.

즉, 메서드의 경우 : { 메서드이름(매개변수: 타입): 반환값타입; } 형태로 메서드에 대한 구조를 정의할 수 있습니다.

프로퍼티 접근

객체 타입이 지정된 변수에 대해서 타입스크립트는 지정된 객체 타입의 구조를 확인하여 프로퍼티와 메서드에 대해서 자동완성(API)을 표시해줍니다.

객체 타입이 지정된 객체의 경우 객체 타입에 작성된 프로퍼티와 메서드에만 접근 가능하며, 이외 프로퍼티나 메서드에 대한 접근에는 에러를 표시하게 됩니다.

옵셔널 프로퍼티 & 옵셔널 메서드

만약 프로퍼티를 가질 수 있을 수 있고 가지지 않을 수 있는 프로퍼티에 대해서는 옵셔널 프로퍼티를 사용할 수 있습니다.

obj 변수에 지정한 객체 타입을 보면 role이라는 프로퍼티를 "?:"으로 작성하였습니다.

role 변수의 타입을 보면 string 또는 undefined로 지정된 것을 확인할 수 있습니다.

그러므로 위 코드처럼 obj 변수가 role 프로퍼티를 갖지 않아도 타입스크립트는 에러를 표시하지 않습니다.

옵셔널 메서드의 경우 메서드 이름 바로 뒤에 ?을 작성해줍니다.

즉, ": { 프로퍼티이름?: 타입; 메서드이름?(매개변수: 타입): 반환값타입; }"으로 작성하여 옵셔널 프로퍼티와 메서드를 작성할 수 있습니다.

읽기 전용 프로퍼티

프로퍼티 이름 앞에 "readonly 키워드"를 통해 읽기 전용 프로퍼티로 지정할 수 있습니다.

위 코드처럼 role 프로퍼티를 읽기 전용 프로퍼티로 지정할 수 있으며 초기화된 이후 값을 변경할 수 없습니다.

읽기 전용 프로퍼티의 경우에는 프로퍼티 값이 변경 불가능하기 때문에 프로퍼티 타입이 const 키워드로 선언한 변수처럼 타입이 추론됩니다.

readonly 키워드는 프로퍼티에만 작성 가능하며 메서드에는 작성할 수 없습니다.

프로퍼티 타입 인덱싱

객체 타입의 특정 프로퍼티 값에 대한 타입에 접근하기 위해 대괄호 표기법을 사용하여 프로퍼티 키로 인덱싱하는 것이 가능합니다.

즉, 타입으로 ": 객체타입['키 값']" 형태를 사용할 수 있습니다.

위 코드처럼 Person 인터페이스에 대해서 대괄호 표기법을 사용하여 특정 프로퍼티 값의 타입에 접근하는 것이 가능합니다.

인덱스 시그니처

인덱스 시그니처란 추가적인 프로퍼티의 "키 타입""값의 타입"을 정의하는 것으로 기존 객체 타입의 경우 존재하는 프로퍼티에 대해서만 접근이 가능했지만, 인덱스 시그니처 작성시 존재하지 않는 프로퍼티에 대한 접근이 허용됩니다.

이때 존재하지 않는 프로퍼티 키 타입과 값 타입은 인덱스 시그니처로 작성된 타입을 따르게 됩니다.

인덱스 시그니처는 ": { [키 이름: 키 타입] : 값 타입; }" 형식으로 사용됩니다.

  • 키 이름은 단지 키를 나타내기 위한 용도로 어떤 이름이든 작성가능합니다.

  • 키 타입의 경우 string이나 number 또는 symbol 타입이어야 합니다. 키 타입은 타입 체크 시점에만 사용되며, 실제 모든 키 타입은 string타입 입니다

    • 키 타입의 경우 string 작성시 키 값이 string 타입으로 변환 가능해야하며, number인 경우에는 number 타입으로 변환 가능한 값만 키 값으로 사용할 수 있습니다.
      예를 들어, 키 타입으로 number 작성한 경우 추가적인 프로퍼티 키 값으로 "key"와 같이 number 타입으로 타입 변환이 불가능한 경우 에러가 발생합니다.

    • number 인덱스 시그니처를 사용하는 것은 혼란을 불러일으킬 수 있습니다. 실제로는 string 타입이지만 number 타입을 사용하기 때문입니다.
      그러므로 number 인덱스 시그니처 대신에 Array, 튜플, ArrayLike 타입을 사용하는 것을 권장합니다.

  • 값 타입의 경우 어떤 타입이든 작성가능합니다. 주의할 점으로 정의된 타입 이외 다른 타입을 갖는 프로퍼티를 가질 수 없게 됩니다.

❗️초과 프로퍼티 검사(Excess Property Checks)

타입스크립트는 구조적 타이핑을 따르며, 서브 타입으로 호환되는 경우 타입 체킹을 통과한다고 했습니다.

하지만 이래의 경우 타입 체킹에서 에러가 발생하게 됩니다.

변수 p가 할당받는 객체의 타입은 { name: string; age: number; gender: string }으로 Person 인터페이스의 서브 타입이지만 이렇게 에러가 발생하는 것을 확인할 수 있습니다.

하지만 아래처럼 변수를 한 번 거치는 경우에는 에러가 발생하지 않습니다.

에러가 발생하는 이유는 "객체 리터럴을 변수에 직접 할당하거나 매개변수에 직접 전달하는 경우 초과 프로퍼티 검사"를 받기 때문입니다.

즉, 객체 리러터럴을 전달받는 변수나 매개변수의 경우 초과 프로퍼티 검사를 거치게 되며 선언된 타입과 객체 리터럴의 타입이 정확하게 일치하지 않는 경우, 다시 말해 프로퍼티를 추가적으로 갖거나 덜 갖는 경우 에러가 발생하게 됩니다.

참고로 객체 타입의 경우 const, let, var 키워드 상관없이 모두 변수를 선언할 때 초기에 할당받는 객체의 구조를 통해 객체 타입이 구체적으로 추론됩니다.

유니온 타입 분리하기

프로퍼티 타입을 명시할 때 유니온 타입으로 작성하는 것보다는 각 프로퍼티 타입에 따라 타입을 분리하여 명시적으로 모델링하는 방법이 관계를 분명하게 만들어줍니다.

위 코드처럼 유니온 타입을 갖는 프로퍼티들을 각 프로퍼티 타입에 따라 분리하여 모델링함으로써 "유효한 상태만을 갖도록" 만드는 것이 좋습니다.

유효한 상태만 표현할 수 있는 타입을 설계하면 상태 정보가 부족하거나 충돌할 가능성을 줄여주게 됩니다.

프로퍼티 추가

일반적으로 타입스크립트의 타입은 변경되지 않습니다. 객체 타입의 경우 지정된 객체 타입의 프로퍼티 이외 프로퍼티에 접근하는 것은 불가능합니다.

그러므로 객체에 "동적으로 프로퍼티를 추가하는 것 또한 불가능"합니다.


이를 해결하기 위해서는 처음부터 객체를 선언할 때 타입도 한번에 정의해야 합니다.


만약 객체를 반드시 나누어 만들어야한다면 객체 스프레드 문법을 사용하는 방법도 존재합니다.

이는 새로운 변수를 선언하고 새로운 타입을 얻도록하는 것입니다.

Array

배열 타입의 경우 포함 관계는 아래와 같습니다.

포함 관계 : unkwown, any > object > Array > Tuple > any

배열의 타입 추론

타입스크립트에서 변수에 다음과 같은 배열을 할당하면 arr 변수는 number[] 타입으로 추론됩니다. 이는 배열의 요소가 모두 숫자 타입으로 숫자 타입의 요소만 갖는 배열로 타입이 추론됩니다.

숫자 타입의 요소만 갖기 때문에 숫자 타입 이외 다른 타입의 값을 추가하면 에러가 발생하게 됩니다.


만약 위 배열처럼 다양한 타입의 값을 요소로 갖는 배열을 경우에는 (number | string | boolean)[] 으로 타입이 추론됩니다. 이는 유니온 타입의 요소를 갖는 배열로 number, string, boolean 타입의 값을 요소로 가질 수 있는 배열로 추론된 다양한 타입의 값을 추가하여도 에러가 발생하지 않습니다.


즉, 타입이 명시하지 않은 변수의 초기값이 배열인경우 두 가지 방법으로 배열 타입이 추론됩니다.

  1. 첫 번째로 할당받는 배열의 요소가 모두 동일한 타입이라면 "type[]"으로 추론됩니다.

  2. 두 번째로는 여러 타입의 값을 요소로 갖는 배열을 할당받는다면 "(type | type ,,,)[]"으로 추론됩니다.

배열 타입 선언

명시적으로 배열 타입을 선언하기 위해서는 : type[]으로 배열 타입을 지정합니다.

// app.ts
var arr1: number[] = [1, 2, 3];
var arr2: string[] = ['a', 'b', 'c'];

배열의 요소에 타입을 지정함으로써 타입스크립트에게 요소의 타입을 알려주게 되고 요소에 접근할 때 타입과 관련된 API가 자동완성으로 표시됩니다. 이외 다른 타입과 관련된 API에 접근시 에러를 표시합니다.

위 그림처럼 배열의 요소를 string 타입으로 지정했기 때문에 각 요소에 대해 접근하게 되면 string과 관련된 API들이 자동완성으로 표시됩니다.

하지만 요소의 타입이 유니온 타입인 경우에는 유니온 타입에 작성된 모든 타입들이 "공통적으로 접근 가능한 API들만 자동완성으로 표시"해줍니다.

위 코드에서 arr 변수의 타입은 (number | string | boolean)[]으로 추론되고 배열은 number, string, boolean 타입의 값을 요소로 가질 수 있습니다.

이때 요소에 접근하게 된다면 number, string, boolean 타입이 공통적으로 갖는 toLocaleString, toString, valueOf 메서드에 대해서만 자동완성이 표시되며 이외 다른 메서드나 프로퍼티에 접근시 에러를 표시하게 됩니다.

이는 타입스크립트가 요소의 타입이 하나의 특정 타입으로 인지하지 못하고 단지 유니온 타입인 것을 인지하기 때문에 확정적으로 접근 가능한 API에 대해서 자동완성을 표시해주며, 이외 API에 접근시 에러를 표시하게 됩니다.

이는 타입 가드라는 것을 사용하여 타입스크립트에게 하나의 특정 타입으로 제한하여 해결할 수 있습니다. 타입 가드에 대해서는 여기에 설명하였습니다.

tuple

튜플 타입의 포함 관계는 아래와 같습니다.

포함 관계 : unkwown, any > object > Array > Tuple > any

튜플이란 타입스크립트에서만 존재하는 타입으로, 배열의 "각 요소의 타입과 배열의 길이를 고정"된 배열을 의미합니다.

이때 할당받을 수 있는 배열은 각 인덱스에 지정한 타입의 값만 가질 수 있으며, "지정한 타입의 개수만큼 요소를 갖는 배열"을 할당받습니다.

즉, 튜플은 "배열의 길이가 고정"되고, "각 요소의 타입이 지정"되어 있는 배열 형식을 의미합니다.

튜플 타입을 지정하기 위해서는 명시적으로 타입을 지정해주어야 합니다. 튜플 타입으로 추론되지는 않습니다.
위에서 살펴보았듯이 다양한 타입의 값을 갖는 배열은 유니온 타입의 요소를 갖는 배열로 추론됩니다.

튜플 타입 선언은 : [ ...type]으로 타입으로 지정합니다. 해당 변수는 배열을 전달받는 변수이며, 할당받는 배열의 각 요소의 타입까지 지정하는 것이 바로 튜플입니다. 이때 배열의 모든 인덱스에 해당하는 요소의 타입을 지정해야 합니다.

이때 타입스크립트가 튜플 타입 [ ...type]을 모델링할 때 "{ index: type; ,,, length: number 리터럴 } 타입으로 모델링"한다는 점입니다.


위 그림에서 tuple이라는 변수의 타입 [number, string]는 실제로 { 0: number, 1: string, length: 2}로 모델링합니다.

이때 할당받는 배열은 추가적인 요소를 가질 수 없으며, 각 인덱스에 해당하는 요소값은 지정한 타입의 값만 가질 수 있습니다.
그리고 유니온 타입과는 다르게 각 인덱스에 해당하는 요소의 타입이 하나의 특정 타입으로 지정되어 있기 때문에 각 타입에 맞는 API를 자동완성으로 표시해주며 접근이 가능합니다.

❗️원본 배열 변경 메서드

Array.prototype.push, Array.prototype.splice. Array.prototype.unshift 메서드로 요소를 추가하는 동작은 예외적으로 튜플에서 허용이됩니다.

즉, 고정된 배열 길이는 "할당에 국한"되어 동작합니다.

push 메서드로 추가하는 경우에만 예외이며, 이외 변수에 배열을 할당하는 경우 정의한 튜플 타입의 길이만큼만 할당할 수 있으며, 추가적으로 요소를 갖거나 더 적게 가질 수 없습니다.

즉, 튜플의 경우 기본적으로 배열의 길이가 고정되어 있지만 push 메서드를 사용하는 경우 예외적으로 허용된다는 점에 주의해야 합니다.

readonly 한정자

배열 타입이나 튜플 타입 앞에 readonly 키워드를 작성하여 배열에 대해서 변경 불가능한 읽기 전용 배열로 사용할 수 있습니다.

포함 관계: unknown, any > object > "readonly 배열(튜플)" > Array > Tuple > any

읽기 전용 배열은 아래와 같은 특징을 갖습니다.

  1. 요소 값 읽기만 가능하며, 요소를 변경하거나 추가하는 것은 불가능

  2. length 값 변경 불가능

  3. push나 pop 메서드처럼 원본 배열을 변경하는 메서드 호출이 불가능

  4. 튜플 타입처럼 각 인덱스에 해당되는 요소값의 타입이 지정

즉, 변경 불가능한 배열의 경우 배열의 요소 값 읽기만 가능하도록 제한합니다.

profile
Frontend Dev

0개의 댓글