
2. Type
타입스크립트의 타입에 대해 알아봅시다.
2024년 11월 08일
Jisoo.
기술블로그
타입스크립트의 함수 및 객체의 타입에 대해서 알아봅시다.
타입스크립트에서 화살표 함수와 문법적으로 유사하게 함수를 표현할 수 있습니다.
1// 문자열 타입 매개변수 a를 가진 반환값이 없는 함수 사용 2function greeter(fn: (a: string) => void) { 3 fn("Hello, World"); 4}
?
를 사용하여 특정 매개변수를 선택적으로 만들 수 있습니다.
1function fn(x? : number) {} 2 3fn(); 4fn(100);
나머지 매개변수는 스프레드 연산자 ...
를 사용하여 정의할 수 있습니다.
1function multiply(n: number, ...m: number[]) { 2 return m.map((x) => n * x); 3} 4 5const a = multiply(10, 1, 2, 3, 4);
호출 가능한 어떤 타입을 선언할 때, 호출 시그니처를 사용할 수 있습니다.
1type DescribableFunction = { 2 description: string; 3 (someArg: number): boolean; 4}; 5 6// fn(6)으로 호출 가능 7function doSomething(fn: DescribableFunction) { 8 console.log(fn.description + " returned " + fn(6)); 9}
자바스크립트에서 this
는 어떻게 선언되느냐에 따라 값이 달라질 수 있습니다.
function
키워드를 이용하여 함수를 선언할 경우 this
는 해당 함수의 컨텍스트를 가리킵니다.
그러나 화살표 함수로 선언할 경우 this
는 글로벌 컨텍스트를 가리키게 되어 에러가 발생합니다.
1// function keyword 2const user = { 3 id: 123, 4 admin: false, 5 // this는 현재의 user 객체를 가리킵니다. 6 becomeAdmin: function () { 7 this.admin = true; 8 }, 9}; 10 11user.becomeAdmin(); // false 12 13-------------------------------------------- 14 15// arrow keyword 16const user = { 17 id: 123, 18 admin: false, 19 // this는 globalThis 20 becomeAdmin: () => { 21 this.admin = true; 22 } 23}; 24 25user.becomeAdmin(); -> Error
이를 이용하여 타입스크립트에서 this
의 타입을 고정할 경우 function
을 사용할 수 있습니다.
1interface User { 2 admin: boolean; 3} 4 5interface DB { 6 filterUsers(filter: (this: User) => boolean): User[]; 7} 8 9const db: DB = getDB(); 10// this는 User 타입을 갖도록 설정됩니다. 11const admins = db.filterUsers(function (this: User) { 12 return this.admin; 13});
동일한 동작을 수행하지만 매개변수의 타입이 다른 경우, 함수 오버로딩을 통해 다양한 매개변수 타입의 동일한 함수를 호출할 수 있습니다.
타입스크립트의 함수 오버로딩을 구현하기 위해서는 구현부가 모든 오버로드의 시그니처를 포함하는 타입이어야 합니다.
1function myPrint(x: string): string; 2function myPrint(x: boolean): boolean; 3function myPrint(x: number): number; 4function myPrint(x: string | boolean | number) { 5 return x; 6} 7 8const a = myPrint("hi"); // string 9const b = myPrint(1); // number 10const c = myPrint(false); // boolean
이러한 방식은 해당 함수의 반환 타입을 명확히 할 수 있다는 장점이 있으나 모든 타입에 대한 경우의 수를 모두 작성해야한다는 단점을 가지고 있습니다.
제너릭을 사용하면 이러한 문제를 간단히 해결할 수 있습니다.
1function myPrint<T>(x : T) : T { 2 return x; 3}
제너릭은 호출 시 매개변수의 타입을 결정하는 기능으로 <T>
형태로 선언할 수 있습니다.
T
는 타입의 별명으로 타입스크립트가 해당 함수 실행 시 자동으로 해당 타입을 추론합니다.
타입 별칭, 인터페이스에서도 제너릭을 사용할 수 있습니다.
1type Box<T> = { 2 content : T 3} 4 5const a : Box<string> = { 6 content : 'gdgd' 7}
제너릭은 인터페이스를 상속하여 특정 프로퍼티를 제한할 수 있습니다.
1interface Lengthwise { 2 length: number; 3} 4 5function loggingIdentity<Type extends Lengthwise>(arg: Type): Type { 6 console.log(arg.length); 7 return arg; 8}
제너릭은 유니언 타입을 만나면 분산적으로 동작합니다.
1type ToArray<Type> = Type extends any ? Type[] : never; 2 3type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]
타입스크립트에서 조건부로 타입을 유추할 수 있습니다.
1type MyType<T> = T extends SomeType ? InferredType : OtherType;
infer
키워드는 타입을 추론할 때 사용하는 키워드입니다.
1// 함수의 반환 타입 추론 2type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; 3 4// 함수의 인자 타입 추론 5type FirstArgument<T> = T extends (arg: infer A) => any ? A : never; 6 7// 배열의 요소 타입 추론 8type ElementType<T> = T extends (infer U)[] ? U : never;
자바스크립트의 경우 객체에 해당하지 않는 프로퍼티에 접근하더라도 에러를 발생하지 않고 조용히 실패합니다.
타입스크립트는 객체의 프로퍼티를 검사하여 해당하지 않는 프로퍼티가 존재할 경우 에러를 발생시킵니다.
1class Shape{} 2 3interface PaintOptions { 4 shape: Shape; 5 xPos?: number; 6 yPos?: number; 7 readonly key : string; 8} 9 10const a : PaintOptions = { 11 shape: new Shape, 12 key: "" 13} 14 15a.zPos = 10; -> Error
인덱스 시그니처는 프로퍼티의 이름을 모르지만, 타입을 알고 있을 때 유용한 방식입니다.
프로퍼티를 [ ]
로 감싸면 인덱스 시그니처를 선언할 수 있습니다.
1interface indexSigniture { 2 // 모든 프로퍼티 이름은 문자열이고 값은 숫자인 인덱스 시그니처 선언 3 [ index : string ] : number; 4 5 length : number; 6 name : string; -> Error 7}
객체를 생성하는 타입이 존재할 경우 구성 시그니처를 사용할 수 있습니다.
호출 시그니처에 new
연산자를 붙여 새로운 객체를 생성합니다.
1type SomeConstructor = { 2 new (s: string): SomeObject; 3}; 4 5function fn(ctor: SomeConstructor) { 6 return new ctor("hello"); 7}
keyof
연산자는 객체의 모든 키를 합친 리터럴 유니온을 생성합니다.
1type Point = { x: number; y: number }; 2type P = keyof Point; // "x" | "y"
타입이 number
, string
인덱스 시그니처를 가진 경우 keyof
는 다음과 같습니다.
1type Arrayish = { [n: number]: unknown }; 2type A = keyof Arrayish; // number 3 4type Mapish = { [k: string]: boolean }; 5type M = keyof Mapish; // string | number
타입의 특정 프로퍼티를 찾기 위해서 인덱싱을 사용하여 접근할 수 있습니다.
1type Person = { age: number; name: string; alive: boolean }; 2 3type I1 = Person["age" | "name"]; // string | number 4type I2 = Person[keyof Person]; // string | number | boolean 5 6// 존재하지 않는 프로퍼티 접근 시 에러 7type I3 = Person["alve"]; -> Error
배열의 임의의 타입을 number
로 인덱싱하여 요소의 타입을 가져올 수 있습니다.
1const MyArray = [ 2 { name: "Alice", age: 15 }, 3 { name: "Bob", age: 23 }, 4 { name: "Eve", age: 38 }, 5]; 6 7// 배열 요소의 타입 가져오기 8type Person = (typeof MyArray)[number];
let, const 로 선언한 값은 인덱싱에 사용할 수 없습니다.
1const MyArray = [ 2 { name: "Alice", age: 15 }, 3 { name: "Bob", age: 23 }, 4 { name: "Eve", age: 38 }, 5]; 6 7type Person = (typeof MyArray)[number]; 8 9let key = "age"; 10 11// 값은 인덱싱에 사용할 수 없습니다. 12type Age = Person[key]; -> Error
Mapped Type
은 어떤 타입의 프로퍼티를 바탕으로 새로운 타입을 생성하는 타입입니다.
1type OptionsFlags<Type> = { 2 // 특정 타입의 프로퍼티를 모두 순회하며 boolean 타입으로 변경 3 [Property in keyof Type]: boolean; 4}; 5 6type FeatureFlags = { 7 darkMode: () => void; 8 newUserProfile: () => void; 9}; 10 11type FeatureOptions = OptionsFlags<FeatureFlags>; 12// FeatureOptions { 13// darkMode : boolean; 14// newUserProfile : boolean; 15// }
타입스크립트의 함수, 객체의 타입에 대해서 알아보았습니다.
다음에는 Utility Type에 대해 알아보겠습니다.
Typescript 시리즈의 다른 포스트
1