본문 바로가기

FrontEnd

타입스크립트 Mapped Type, KeyOf, TypeOf 정리

반응형

해당 글의 조회수가 꽤 나와서 내용을 보강하였습니다.

참고로 끼워넣은 게시물들도 읽어주세요...

0. typeof 연산자

해당 연산자는 자바스크립트에도 존재하지만 타입스크립트 타입, 인터페이스 문법에도 확장하여 사용할 수 있다.

기본적으로 값은 "값 공간"에 존재한다.

값 공간에 있는 값에 typeof 연산자를 적용하여 값 공간의 값을 타입 공간에서 타입으로 이용할 수 있다.

타입 공간과 타입 계층에 대해 더 잘 알고 싶으면 아래 글을 참고한다.

https://itchallenger.tistory.com/447?category=1091150 

 

[Typescript][타입스크립트][The Type Hierarchy Tree][타입 계층 트리]

원문 : https://www.zhenghao.io/posts/type-hierarchy-tree Zhenghao Software Engineer www.zhenghao.io 먼저 아래 표를 보자 https://www.typescriptlang.org/docs/handbook/type-compatibility.html#any-unkno..

itchallenger.tistory.com

let s = "hello";
let n: typeof s; // let n : string
const add1 = (x:number)=>x+1
type MyAdd1Type = typeof add1 // (x:number)=>number

// 아래는 오류가 발생한다. 이유를 생각해보자.

function f() {
  return { x: 10, y: 3 };
}
type P = ReturnType<f>; // 'f' refers to a value, but is being used as a type here. Did you mean 'typeof f'?
// <>안에는 타입이 들어가야 하기 때문이다.

// 해당 문법은 정상동작한다.
function f() {
  return { x: 10, y: 3 };
}
type P = ReturnType<typeof f>;

type P = {
    x: number;
    y: number;
}

1. 들어가며 : 타입 대수

TypeScript는 Set Theory과 매우 관련이 있으며
타입스크립트의 타입에 대한 합집합 및 교집합 연산은 집합 이론의 대수 법칙을 따른다.
TypeScript의 맥락에서 이를 타입 대수라고 부르자.
  1. 교집합 연산자는 합집합 연산에 분배할 수 있습니다
    • A & (B | C) === (A & B) | (A & C).  A의 교집합(&) 연산을 B | C에 분배했습니다.
  2. 합집합 연산자도 교집합 연산에 분배할 수 있습니다.
    •   A | (B & C) === (A | B) & (A | C).

이 내용이 중요한 이유는, 모든 타입을 단일 필드 오브젝트들의 교집합(&)과 합집합(|)으로 생각할 수 있기 때문이다.

 

type Test = 'A' | 'B' | 'C';
type MappedTest = { [K in Test]: number };
// 위는 아래와 같다.
type MappedTest = {'A' : number} &  {'B' : number} &  {'C' : number};

타입들을 합집합 기호(|) 단위로 잘 분해하는 것이 중요하다. 유니온의 각 타입들은 직교하기 때문이다.

type Conference = {type: 'conference', isVirtual: boolean} 
type Meetup = {type: 'meetup', isVirtual: false} 
type TechEvent = Conference | Meetup
type IsVirtual = {isVirtual: true} 

type VirtualEvent = IsVirtual & TechEvent
// 타입 대수를 통해 세 가지 표현을 얻을 수 있다.
type VirtualEvent = IsVirtual & TechEvent
type VirtualEvent = IsVirtual & (Conference | Meetup)
type VirtualEvent = (IsVirtual & Conference) | (IsVirtual & Meetup)
type VirtualEvent =  ({isVirtual: true}  & {type: 'conference'}) | ({isVirtual: true}  & {type: 'meetup'})

이 글을 이해하는데 있어 위 내용만 알고 있으면 충분하지만, 더 자세한 내용은 아래를 참고한다.

https://itchallenger.tistory.com/448?category=1091150 

 

[Typescript][Type Algebra][타입스크립트][타입 대수]

https://www.zhenghao.io/posts/type-algebra Zhenghao Software Engineer www.zhenghao.io Type algebra는 TypeScript에서 많이 알려지지 않은 주제이며 TypeScript의 몇 가지 단점을 이해하는 데 필수적인 주제..

itchallenger.tistory.com

2. keyof 연산자

이미 존재하는 오브젝트를 사용한 타입 지정이 가능하다.

// no index signature
type Point = { x: number; y: number };
type P = keyof Point; // type P = 'x' | 'y'

// with index signature
type Arrayish = { [n: number]: unknown };
type A = keyof Arrayish; // type A = number

// 자바스크립트 오브젝트 키는 스트링 타입으로 반드시 강제변환되기 때문에 숫자도 허용한다.
type Mapish = { [k: string]: boolean };
type M = keyof Mapish; // type M = string | number

3. Mapped Type

기존 정의된 타입을 새로운 타입으로 변환해주는 문법이다.

유니온 타입의Mapped Type

type Test = 'A' | 'B' | 'C';
type MappedTest = { [K in Test]: number };
const mappedTest: MappedTest = {
  A: 1,
  B: 2,
  C: 3,
}

인터페이스, 타입을 이용한 Mapped Type

type Subset<T> = {
  [K in keyof T]?: T[K];
}

const mappedTest: Subset<MappedTest> = {
  A: 1, // 생략 가능
  B: 2, // 생략 가능
  C: 3, // 생략 가능
}

예제 : 타입 바꾸기

type OptionsFlags<Type> = {
  [Property in keyof Type]: boolean;
};

type FeatureFlags = {
  darkMode: () => void;
  newUserProfile: () => void;
};

type FeatureOptions = OptionsFlags<FeatureFlags>;
// 위와 동일한 표현임.
type FeatureOptions = {
    darkMode: boolean;
    newUserProfile: boolean;
}

예제 : 모디파이어 적용 - readonly 제거

mutability(readonly)와 optionality(?)를 적용할 수 있다.

//  'readonly' 속성 제거
type CreateMutable<Type> = {
  -readonly [Property in keyof Type]: Type[Property]; // +readonly하면 추가함. default는 mutable(-readonly)
};

// readonly 타입
type LockedAccount = {
  readonly id: string;
  readonly name: string;
};

type UnlockedAccount = CreateMutable<LockedAccount>;
// 변환된 타입. mutable 타입.
type UnlockedAccount = {
    id: string;
    name: string;
}

예제 : Optionality 제거

// 'optional' 제거. 전부 필수 필드로
type Concrete<Type> = {
  [Property in keyof Type]-?: Type[Property]; //  +?하면 optional하게 바꿈. default는 필수
};

// 'optional' 추가
type Optional<Type> = {
  [Property in keyof Type]+?: Type[Property]; 
};

type MaybeUser = {
  id: string;
  name?: string;
  age?: number;
};

type User = Concrete<MaybeUser>;
// 위와 같음
type User = {
    id: string;
    name: string;
    age: number;
}

예제 : template literal 이용하여 기존 필드명 이용해 새로운 프로퍼티 만들기. (get + 필드명)

// template literal types 처럼 새로운 프로퍼티 이름 만들기.

type Getters<Type> = {
    [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};

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

type LazyPerson = Getters<Person>;
// 위와 같음
type LazyPerson = {
    getName: () => string;
    getAge: () => number;
    getLocation: () => string;
}

예제 : 속성명으로 해당 속성 제거하기 - as 사용

as 뒤의 타입이 never로 평가되면 해당 키가 날라간다.

// 해당 타입의 키들에 필터를 거는것처럼 작동한다.
type RemoveKindField<Type> = {
    [Property in keyof Type as Exclude<Property, "kind">]: Type[Property]
};

interface Circle {
    kind: "circle";
    radius: number;

type KindlessCircle = RemoveKindField<Circle>;
// 위와 같음
type KindlessCircle = {
    radius: number;
}

예제 :  value에 해당 키가 존재하는지 확인하기

extends는 마치 등호처럼 사용된다. value가 {pil:true} 타입에 할당 가능하면 해당 키의 값을 true, false로 매핑한다.

type ExtractPII<Type> = {
  [Property in keyof Type]: Type[Property] extends { pii: true } ? true : false;
};

type DBFields = {
  id: { format: "incrementing" };
  name: { type: string; pii: true };
};

type ObjectsNeedingGDPRDeletion = ExtractPII<DBFields>;
// 위와 같음
type ObjectsNeedingGDPRDeletion = {
    id: false;
    name: true;
}

 

반응형