Lsiron

Type Script 의 Object 심화 (index signature, recursive, keyof, 타입변환기) 본문

언어/Type Script

Type Script 의 Object 심화 (index signature, recursive, keyof, 타입변환기)

Lsiron 2024. 7. 18. 16:42

index signature

Index Signature 는 객체의 속성 키가 동적으로 정의될 수 있는 경우 유용하다.

 

Index Signature 는 객체의 속성 이름이 특정 타입이고, 그 속성의 값이 다른 특정 타입임을 지정한다.

 

이는 객체가 동적 프로퍼티를 가질 때, 예를 들어 동적으로 추가되는 속성의 타입을 정의하고 싶을 때 매우 유용하다.

 

즉, 객체의 속성이 여러개 일 때, 한번에 타입을 지정 할 수 있는 방법이 바로 index signature를 사용하는 방법이다.

 

기본사용

interface StringNumberDictionary {
  [key: string]: number;
}

const scores: StringNumberDictionary = {
  alice: 10,
  bob: 15,
  charlie: 20
};

console.log(scores.alice); // 10
console.log(scores["bob"]); // 15

 

위 예시처럼 alice, bob, charlie 속성에 각각 타입을 지정하지 않아도 index signature 를 통해 한번에 속성의 타입을 number로 지정할 수 있다.

 

인덱스 시그니처와 다른 프로퍼티의 혼합 사용

 

인덱스 시그니처를 사용하면서 고정된 프로퍼티를 함께 사용할 수 있다.

 

이 경우, 고정된 프로퍼티의 타입은 인덱스 시그니처의 타입과 호환되어야 한다. 

 

즉, 고정된 프로퍼티 타입과 인덱스 시그니처의 타입이 적어도 한 개는 일치해야 한다는 것 이다.

interface StringNumberDictionary {
  [key: string]: number;
  length: number; // 고정된 프로퍼티
}

const data: StringNumberDictionary = {
  length: 2,
  a: 1,
  b: 2
};

 

숫자 키를 사용하는 인덱스 시그니처 ( 속성이름이 숫자 인 경우 )

 

숫자 키를 사용하는 인덱스 시그니처를 정의할 수도 있다.

interface NumberArray {
  [index: number]: string; // key:number, key:string도 가능하다.
}

const myArray: NumberArray = ["Hello", "World"];

console.log(myArray[0]); // "Hello"
console.log(myArray[1]); // "World"

 

문자열과 숫자 인덱스의 혼합 사용

 

숫자 인덱스 시그니처를 사용하면 TypeScript는 문자열 인덱스도 해당 타입으로 간주한다.

 

이는 자바스크립트 객체의 속성이 항상 문자열로 변환되는 점을 반영한다.

interface AnotherType {
  [index: number]: string;
  [key: string]: string | number;
}

const example: AnotherType = {
  0: "Zero",
  1: "One",
  name: "Example",
  length: 2
};

console.log(example[0]); // "Zero"
console.log(example["name"]); // "Example"

 

인덱스 시그니처를 사용할 때 주의할 점은, 모든 고정된 프로퍼티의 타입이 인덱스 시그니처의 타입과 호환되어야 한다는 것이다.

 

예를 들어, 아래와 같은 코드는 오류를 발생시킨다.

interface InvalidExample {
  [key: string]: number;
  name: string; // 오류: 'name' 속성은 'number' 타입이어야 함
}

 

recursive 타입지정?

Recursion는 어떤 함수나 타입이 자기 자신을 참조하여 정의되는 것을 말한다. 즉, 재귀적 용법이라는 뜻인데,

 

recursive 타입지정이란, 재귀적으로 타입을 지정 해 주는 것이다.

 

TypeScript에서 재귀적으로 타입을 지정하는 방법은 타입 정의 안에서 자기 자신을 참조하는 형태로 이루어진다.

 

주로 트리 구조나 중첩된 객체 구조를 표현할 때 사용된다.

interface MyType {
    'font-size' : MyType | number
}

let css :MyType = {
    'font-size' : {
        'font-size' : {
            'font-size' : 14
        }
    }
}

 

속성 값에 타입명을 다시 넣어줌으로써 재귀적으로 타입을 지정 해 줄 수 있다.

 

내부에 중첩해서 코드를 만들 일이 있을 때 사용해보자.

 

트리 구조

 

트리, 그래프, 계층적 데이터 구조를 표현할 때 사용한다.

interface TreeNode {
  value: number;
  children?: TreeNode[]; // TreeNode 타입의 배열을 가질 수 있음
}

const tree: TreeNode = {
  value: 1,
  children: [
    {
      value: 2,
      children: [
        { value: 4 },
        { value: 5 }
      ]
    },
    {
      value: 3,
      children: [
        { value: 6 },
        { value: 7 }
      ]
    }
  ]
};

 

JSON 데이터 구조

 

중첩된 객체나 배열을 표현할 때 사용한다.

type Json = string | number | boolean | null | Json[] | { [key: string]: Json };

const jsonData: Json = {
  name: "Alice",
  age: 30,
  isStudent: false,
  scores: [95, 82, 77],
  address: {
    city: "Wonderland",
    zipCode: "12345"
  }
};

 

허나 사용할 일은 별로 없다.

 

keyof ?

keyof 연산자는 TypeScript의 유틸리티 타입 중 하나로, 객체 타입의 키(key)를 문자열 리터럴 유니온 타입으로 추출하는 데 사용된다. 이는 주로 객체의 키 타입을 제네릭하게 다룰 때 유용하다.

 

즉, keyof 연산자는 Object의 모든 키를 추출하는 연산자이다.

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

type PersonKeys = keyof Person; // "name" | "age" | "isStudent"

const key1: PersonKeys = "name"; // 유효
const key2: PersonKeys = "age"; // 유효
const key3: PersonKeys = "isStudent"; // 유효
const key4: PersonKeys = "address"; // 오류: 'address'는 'PersonKeys'에 할당될 수 없음

 

keyof 연산자는 object의 키 들을 이용해서 타입을 체크 할 때 종종 사용한다.

 

index signature에 keyof를 쓰면 어떻게 될까 ?

interface Person {
    [key : string] : number
  }
  
  type PersonKeys = keyof Person; // string | number

 

Object 자료는 숫자를 객체 키에 넣어도 문자로 치환 해 주기 때문에 string | number로 나온다.

 

타입변환기 ?

TypeScript에서 매핑된 타입(Mapped Types)은 객체 타입의 키를 사용하여 새로운 타입을 생성하는 강력한 도구이다.

 

매핑된 타입을 사용하면 기존 타입을 변환하여 새로운 타입을 쉽게 정의할 수 있다. 

 

보통 Object의 타입을 바꾸고싶을 때 타입변환기를 만들어서 타입을 변환시켜준다고 한다.

 

기본 매핑된 타입은 객체 타입의 모든 키를 순회하며 각 키에 대해 새로운 타입을 정의한다.

 

이는 in 연산자외 위에서 배운 keyof 연산자를 사용하여 구현된다.

type Person = {
    name : string,
    age : number,
    isStudent : boolean
}

type TypeChanger<T> = {
    [K in keyof T] : string   // [자유작명 in keyof 타입파라미터] : 원하는 타입
}

type NewType = TypeChanger<Person>

// NewType은 아래와 같이 변환이 된다.
type NewType = {
    name: string;
    age: string;
    isStudent: string;
}

 

generic 함수를 사용하여 TypeChanger의 타입 파라미터를 'T' 로 입력 해 주었고, NewType으로 타입을 생성할 때, 인자에 Person 타입을 입력하였다.

 

이 Person 타입은 'K in keyof T' 의 T에 들어가게 된다.

 

keyof 연산자를 통해 T에 들어간 Person 타입의 모든 키를 추출하여 리터럴 유니온타입으로 만들어주고,

 

in 연산자를 통해 왼쪽에 있는 K 값이 오른쪽에 있는 리터럴 유니온타입에 있으면 모두 string으로 바꾸어주라는 뜻이다.

 

정리하자면 아래와 같다.

 

1. keyof T : keyof T 는 'name' | 'age' | 'isStudent' 가 된다.

 

2. K in keyof T : 매핑된 타입에서 사용되는 반복구문이다. K('T' 의 키를 반복적으로 참조하는 임시 변수)는 keyof T로부터 추출된 각 키를 의미하기 때문에, K 는 'name', 'age', 'isStudent' 값을 순차적으로 가진다.

 

3. 매핑된 타입 생성 : type NewType = { name: string; age: string; isStudent: string;}

 

참조: 코딩 애플