목차
2.28 원시 자료형에도 브랜딩 기법을 사요할 수 있다
: string, number 같은 원시 자료형 타입도 더 세밀하게 구분 가능
킬로미터를 마일로 바꾸는 함수 코드
function kmToMile (km : number) {
return km * 0.62;
}
const mile = kmToMile(3);
- 문제 : 함수에 넣는 '3'이라는 숫자가 마일인지, 킬로미터인지 알 수 없음
- 킬로미터, 마일 타입 존재 X
-> 브랜딩 기법으로 구체적 타입 지정
type Brand<T,B> = T & {__brand : B};
type KM = Brand<number, 'km'>;
type Mile = Brand<number, 'mile'>;
function kmToMile (km : KM) {
return km * 0.62 as Mile;
}
const km = 3 as KM;
const mile = kmToMile(km);
const mile2 = 5 as Mile;
kmToMile(mile2); //오류 - 매개변수로 KM 타입만 들어갈 수 있기 때문
- Brand 타입 : number 타입에 각자의 브랜드 속성 추가해 서로 구별
- 원래 자료형 + 새로운 자료형
- KM : number & {__brand: 'km'}
- Mile : number & {__brand: 'mile'}
- KM, Mile 타입
- as 강제 변환 필수 (기존 타입이 아니기 때문)
- 대신 한번만 하면됨
2.29 배운 것을 바탕으로 타입을 만들어보자
: 유용한 타입 만들기
2.29.1 판단하는 타입 만들기
: 특정 타입이 무슨 타입인지 알기
-> 해당 타입을 컨디셔널 타입으로 제거 및 추리기 가능
(any는 안쓰는게 좋음)
IsNever
: never인지 판단
type IsNever<T> = [T] extends [never] ? true : false;
- 배열 사용 -> T에 never를 넣을 때, 분배법칙 발생을 막기 위해
1. 제네릭과 유니언이 만나면 분배법칙이 실행됩니다. never는 그 자체로 유니언이라서 분배법칙이 일어나나, never는 "공집합"이라서 분배할 게 없습니다. never는 그냥 never입니다. boolean같은 것이나 true | false로 쪼개지는 것이죠.
2. 분배법칙에서는 never extends string 자체가 실행되지 않습니다. 그래서 그냥 never입니다.
3. 그냥 편하게 never extends X 는 무조건 never라고 생각하셔도 됩니다.(never 자리가 제네릭인 경우)
https://www.inflearn.com/community/questions/1102922?focusComment=303009
제네릭에 never가 들어올 때 분배법칙 - 인프런 | 커뮤니티 질문&답변
누구나 함께하는 인프런 커뮤니티. 모르면 묻고, 해답을 찾아보세요.
www.inflearn.com
IsAny
: any 타입인지 판단
type IsAny<T> = string extends (number & T) ? true : false;
- string과 numberdms 절대 겹칠 수 없음 -> extend 불가
- 예외 : any
- number & any -> any
- string은 any를 extends 가능
-> T 가 any일 경우에만 true
IsArray
T extends unknown[] ? true : false
일 경우 never, any, readonly [] 타입을 걸르지 못함
type IsNever<T> = [T] extends [never] ? true : false;
type IsAny<T> = string extends (number & T) ? true : false;
type IsArray<T> = IsNever<T> extends true
? false
: T extends readonly unknown[]
? IsAny<T> extends true
? false
: true
:false;
- IsNever<T> extends true
- IsArray<never> 가 never 되는 것을 막기 위함
- IsAny<T> extends true
- IsArray < any > 가 boolean이 되는 것을 막기 위함
- T extends readonly unknown[]
- IsArray < readonly [] > 가 false 되는 것을 막기 위함
IsTuple
: 배열에서 튜플만 판단
: 튜플이 아닌 배열 타입은 false
type IsNever<T> = [T] extends [never] ? true : false;
type IsTuple<T> = IsNever<T> extends true
? false
: T extends readonly unknown[]
? number extends T ["length"]
? false
: true
: false;
- 배열과 튜플의 차이점
- 튜플 : 길이 고정 ex) 1,2,3 (숫자로 고정)
- 배열을 lenth할 경우 -> number 타입이 나옴
- number extends T["length"]는 false (튜플)
- 위 코드에서 any 걸러짐
- any["length"] -> any : number extends any -> true
IsUnion
type IsUnion<T, U=T> = IsNever<T> extends true
? false
: T extends T
? [U] extends [T]
? false
: true
: false;
- T extends T : 분배법칙을 만들기 위함
2.29.2 집합 관련 타입 만들기
: ts의 타입은 집합의 원리를 충실하게 따름
: 다양한 집합의 연산, 특성을 타입으로 나타내보기
차집합 만들기
type Diff<A,B> = Omit<A & B, keyof B>;
type R1 = Diff<{name : string, age: number}, {name : string, married: boolean}> // {age : number}
- Omit : 특정 객체에서 지정한 속성 제거
- Omit<A & B, keyof B>
- {name : string, age: number, married: boolean} - name | married = age
대칭 차집합 (합집함 - 교집합)
type SymDiff<A,B> = Omit<A & B, keyof (A|B)>;
// type R1 = {age : number, married: boolean}
type R1 = SymDiff<{name : string, age: number}, {name : string, married: boolean}>
위의 차집합과 대칭 차집합은 객체에만 적용 가능 (유니언에는 적용 X)
유니언에서 대칭 차집합 적용하기
type SymDiffUnion<A,B> = Exclude<A | B, A & B>;
type R3 = SymDiffUnion<1|2|3, 2|3|4> //1|4
- Exclude : 어떤 타입 (A | B)에서 다른 타입 (A & B) 제거하는 타입
부분집합 알아보기
type IsSubset <A,B> = A extends B ? true : false;
type R1 = IsSubset<string, string | number>; //true
type R2 = IsSubset<{name:string, age:number}, {name:string}>; //true
type R3 = IsSubset<symbol, unknown> //true
두 타입이 동일한지 확인
- 두 집합이 서로의 부분 집합이면 동일한 집합임
type Equal<A, B> = A extends B ? B extends A ? true : false :false;
위 코드는 허점 존재
type Equal<A, B> = A extends B ? B extends A ? true : false :false;
type R1 = Equal<boolean, true | false> //booelan
type R2 = Equal<never, never> //never
- booelan, never는 유니언으로 분배법칙 발생
(해결)
type Equal<A, B> = [A] extends [B] ? [B] extends [A] ? true : false :false;
type R1 = Equal<boolean, true | false> //true
type R2 = Equal<never, never> //true
하지만 any는 구별하기 못함
type R3 = Equal<any, 1> //true
type R4 = Equal<[any], [number]> //true
(해결)
type Equal2<X,Y>
= (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2)
? true : false
2.30 타입스크립트의 에러 코드로 검색하자
: 타입스크립트의 에러 메세지 끝에는 항상 숫자 존재
- 2345 -> 'TS2345'로 검색하면 해결 가능
숫자 = 에러 유형
-> 에러 발생 시 해결에 도움을 줌
2.31 함수에 기능을 추가하는 데코레이터 함수가 있다
: 5.0에서 데코레이터 함수가 정식 추가됨
: 데코레이터 = 클래스의 기능을 증강하는 함수
: 여러 함수에서 공통으로 수행되는 부분을 데이코레이터로 만들면 유용함
코드 반복 예
class A {
eat() {
console.log('start');
console.log('eat');
console.log('end');
}
work() {
console.log('start');
console.log('work');
console.log('end');
}
sleap() {
console.log('start');
console.log('sleep');
console.log('end');
}
}
- start와 end를 로깅하는 코드가 중복
데코레이터로 해결
function startAndEnd(originalMethod: any, context : any) {
function replacementMethod(this: any, ...args: any[]) {
console.log('start');
const result = originalMethod.call(this , ...args);
console.log('end');
return result;
}
return replacementMethod;
}
class A {
@startAndEnd
eat() {
console.log('eat');
}
@startAndEnd
work() {
console.log('work');
}
@startAndEnd
sleap() {
console.log('sleep');
}
}
- originalMethod : eat, work, sleap 같은 기존 메소드
- 이 메서드가 replacementMethod(대체 메서드) 로 바뀜
- replacementMethod에 따라 기존 메서드의 호출 전후로 start, end가 로깅됨
위 레코레이터를 더 제대로 타이핑하면
function startAndEnd<This, Args extends any[], Return>(
originalMethod: (this : This, ...args : Args) => Return,
context : ClassMethodDecoratorContext<This, (this: This, ...args : Args) => Return>) {
function replacementMethod(this: This, ...args: Args) {
console.log('start');
const result = originalMethod.call(this , ...args);
console.log('end');
return result;
}
return replacementMethod;
}
- This, Args, Return 타입 매개변수 : 기존 메서드의 this, 매개변수, 반환값
- 그대로 대체 메서드에 적용
- context : 데코레이터의 정보를 갖고 있는 매개변수
- startAndEnd 데코레이터는 클래스 메서드를 장식 -> context는 ClassMethodDecoratorContext가 됨
context는 다양한 종류가 있는데, 어떤 문법을 장식하냐에 따라 context의 타입을 교체하면 됨
2.32 앰비언트 선언도 선언 병합이 된다
: 타입스크립트에서 남의 라이브러리를 사용할 경우, 그 라이브러리가 js라면 직접 타이핑해야 함
: 앰비언트 선언 = 이 경우 사용하는 것
: declare 예약어 사용
declare namespace NS {
const v : string;
};
declare enum Enum {
ADMIN = 1
}
declare function func(parma : number) : string;
declare const variable : number;
declare class C {
constructor(p1: string, p2 : string);
};
new C (func (variable), NS.v);
- 코드 구현부 X
- func, class C, 변수 variable : 타입만 존재
- 그럼에도 new C (func (variable), NS.v)와 같이 값으로 사용 가능
- 외부 파일에 실제 값이 존재한다고 믿음
- 실행 시 외부 값에 값이 없으면 에러
- namespace와 enum 을 declare로 선언하는 이유
- 내부 멤버의 구현 생략 가능
- enum을 declare하면 js로 변환할때 실제 코드가 생성되지 않음
- declare 쓰는 경우 이미 다른 곳에 실제 값이 있다고 생각하기 떄문
인터페이스와 타입 별칭도 declare로 선언 가능
declare interface Int {}
declare type T = number;
- 하지만 인터페이스와 타입 별칭은 declare로 선언하지 않아도 동일하게 작용 -> 굳이 사용 X
타입스크립트에서 선언이 생성하는 개체
유형 | 네임스페이스 | 타입 | 값 |
네임스페이스 | O | O | |
클래스 | O | O | |
enum | O | O | |
인터페이스 | O | ||
타입 별칭 | O | ||
함수 | O | ||
변수 | O |
- 타입 선언 시 사용될 수 있는 용도 정리
같은 이름으로 다른 선언과 병합가능 여부
병합 가능 여부 | 네임스페이스 | 클래스 | enum | 인터페이스 | 타입 별칭 | 함수 | 변수 |
네임스페이스 | O | O | O | O | O | O | O |
클래스 | O | X | X | O | X | O | X |
enum | O | X | O | X | X | X | X |
인터페이스 | O | O | X | O | X | O | O |
타입 별칭 | O | X | X | X | X | O | O |
함수 | O | O | X | O | O | O | X |
변수 | O | X | X | O | O | X | X |
- 인터페이스, 네임스페이스 병합이나 함수 오버로딩을 제외하고는 같은 이름으로 선언은 피하기
선언 병합을 활용하면 좋은 경우
declare class A {
constructor(name : string);
}
function A(name :string) {
return new A(name);
}
new A('zerocho');
A('zerocho');
- 클래스가 있을 때 new를 붙이지 않아도 되는 코드
- class A : 앰비언트 선언
- function A : 일반 선언
- 클래스와 함수는 병합됨 (표 참고)
- declare로 앰비언트 선언한 타입과 일반 선언한 타입끼리도 병합됨
선언 병합의 다른 경우
function Ex() {
return 'hello';
}
namespace Ex{
export const a = 'world';
export type B = number;
}
Ex(); //hello
Ex.a; //world
const b : Ex.B = 123;
- js의 함수는 객체 -> 속성 추가 가능
- 함수와 네임스페이스는 병합 가능해 위의 코드 에러 X
'typescript' 카테고리의 다른 글
lib.es5.d.ts 분석하기 - 3.7 ~ 3.11 (0) | 2024.11.10 |
---|---|
lib.es5.d.ts 분석하기 - 3.1 ~ 3.6 (2) | 2024.11.03 |
2.23 ~ 2.27 (0) | 2024.10.13 |
2.18 ~ 2.22 (1) | 2024.10.06 |
타입스크립트 2.13 ~ 2.17 (0) | 2024.09.28 |