공식문서 ref) https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html
const a:string|number;
if(typeof a === 'string'){
....
} // 이때는 a가 string type
if(typeof a === 'number'){
....
} // 이때는 a가 number type..
function isString(value: unknown) {
return typeof value === 'string'
}
if (isString(a)) {
console.log(a) // string | number
}
ref) https://www.typescriptlang.org/docs/handbook/2/narrowing.html#typeof-type-guards
https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions
먼저 공식문서를 통해 type을 narrowing 하는 개념을 remind 해보고 가자.
타입가드는 js 문법을 통해 type을 좁히는 방법이다.
js의 if문, typeof, in, instanceof.. 등 Javascript 문법을 이용해 타입을 좁히는 것이 핵심
typeof 예시
function printAll(strs: string | string[] | null) {
if (strs && typeof strs === "object") {
for (const s of strs) {
console.log(s);
}
} else if (typeof strs === "string") {
console.log(strs);
}
}
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) {
return animal.swim();
}
return animal.fly();
}
function logValue(x: Date | string) {
if (x instanceof Date) {
console.log(x.toUTCString());
(parameter) x: Date
} else {
console.log(x.toUpperCase());
(parameter) x: string
}
}
JS 문법으로 Type을 Narrowing 하는 것이 아니라, TS 문법으로 direct로 control 하는 방식이다.
[parameterName is Type] 형식으로 사용하고, 여기서 오는 parameterName은 함수가 인자로 받는 parameter 중에 하나의 name과 동일해야 한다.
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];
const underWater1: Fish[] = zoo.filter(isFish);
// or, equivalently
const underWater2: Fish[] = zoo.filter(isFish) as Fish[];
// The predicate may need repeating for more complex examples
const underWater3: Fish[] = zoo.filter((pet): pet is Fish => {
if (pet.name === "sharkey") return false;
return isFish(pet);
});
Type 단언은 type에 대한 정보를 알고는 있지만, TS가 알 수 없을때 사용한다. (타입을 좁히는 것은 아니다)
아래와 같은 코드에서, document.getElementById("main_canvas")의 결과로 HTMLCanvasElement를 return할 것이라는 결과를 개발자는 알고 있지만, TS는 이를 추론할 수 없다.
이럴때 assertion을 통해 => HTMLCanvasElement를 명시적으로 return한다고 TS에게 알려주는 것.
Type Narrowing이 필요할 때 Type assertion을 사용하지 않도록 사용을 주의 할 필요가 있다.
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
아래와 같은 코드가 있다 => 여기서 먼저 list는 (string | null | undefind)[] 타입으로 추론된다.
filterNullAndUndefinedList는 filter 함수를 통해 null과 undefined를 제거한 배열이다.
하지만, 5.5 이전의 버전에서는 type predicate 기능이 없었기 때문에, 5.4.5 버전으로 컴파일 한 결과는 여전히 동일한 타입을 추론된다.
const list = ['1', '2' , null, undefined];
const filterNullAndUndefinedList = list.filter(val => val !== undefined && val !== null);
console.log(filterNullAndUndefinedList); // ['1', '2'] but (string | null | undefind)[] 로 추론
function filterNullFn<T>(value : T | null | undefined):value is T{
return value !== undefined && value !== null;
}
const filterNullAndUndefinedListByFn = list.filter(filterNullFn);
console.log(filterNullAndUndefinedListByFn)
5.5이상 버전부터는 obj[key]의 type을 narrowing 하는것이 가능해졌다.
obj[key]의 결과값을 곧바로 narrowing하면 TS는 인식하지 못하는 문제가 있었기 때문.(타입가드가 안되어서 unknown으로 추론됨)
//5.5이전버전
function f1(obj: Record<string, unknown>, key: string) {
const value = obj[key];
if (typeof value === "string") {
value.toUpperCase();
}
}
//5.5 버전
function f1(obj: Record<string, unknown>, key: string) {
if (typeof obj[key] === "string") {
// Now okay, previously was error
obj[key].toUpperCase();
}
}
보통 IndexSignature은 객체에 key를 통해 동적으로 아이템을 추가할 때 사용한다.
하지만 동적으로 추가하는 것이기 때문에 key값을 제한하고 싶은 경우가 있다.
type TIndexSignature = {
[key: string]: string;
};
type TItemNameKey = `itemName${number}`;
type TIndexSignature = {
[key: TItemNameKey]: string;
};
type addPrefix<TKey, TPrefix extends string> = TKey extends string
? `${TPrefix}${TKey}`
: never;
type TKey = string
const a : addPrefix<TKey, 'itemName'> = 'itemName231'
ref) Utility Type :https://dev.to/teamradhq/utility-type-withprefix-eje
export type WithPrefix<Prefix extends string, T, Separator extends string = '/'> = {
[K in keyof T as `${Prefix}${Separator}${string & K}`]: T[K];
};
// T를 받아서 => K 는 keyof T => 이를 Prefix, Sepeartor가 붙은 값으로 제한
type UserApi = {
get: string;
set: string;
all: string;
};
const api: WithPrefix<'/api', UserApi> = {
'/api/get': '',
'/api/set': '',
'/api/all': '',
};
ref) https://www.totaltypescript.com/concepts/the-prettify-helper
Union Type을 보기 편하게 해주는 Utility Type. Global Utility Type이 아니므로 직접 정의해서 사용해야함.
type Prettify<T> = {
[K in keyof T]: T[K];
} & unknown;
type Intersected = {
a: string;
} & {
b: number;
} & {
c: boolean;
};
/**
* { a: string; } & { b: number; } & { c: boolean; }
*/ //이렇게 보임
type Intersected = Prettify<
{
a: string;
} & {
b: number;
} & {
c: boolean;
}
>;
/**
* {
* a: string;
* b: number;
* c: boolean;
* }
*/ //묶여서 보임
ref) https://www.totaltypescript.com/how-to-type-array-reduce
주로 3번을 사용하지만, 3가지 방법모두 한번 봐두면 나쁠건 없다.
const grouped = array.reduce((obj, item) => {
obj[item.key] = item.value;
return obj;
}, {} as Record<string, string>);
const grouped = array.reduce(
(obj: Record<string, string>, item) => {
obj[item.key] = item.value;
return obj;
},
{}
);
const grouped = array.reduce<Record<string, string>>(
(obj, item) => {
obj[item.key] = item.value;
return obj;
},
{}
);