Typescript has some additional reserved keywords that allows set new types, based on other types. The most spread is Infer, Omit, Pick, Record.
Let’s go from the beginning.
Generics in Typescript — it is the type that depends on another type. By example, we have a type Location. We parameterize it with a certain parameter <T>.
Let’s write many types parameters, separated by a comma. We add necessary values when calling it.
Typescript는 다른 타입을 기반으로 새로운 타입을 설정할 수 있는 추가 예약된 키워드를 가지고 있습니다. 가장 널리 사용되는 것은 Infer, Omit, Pick, Record입니다.
처음부터 시작해 보죠.
Typescript의 제네릭 - 다른 타입에 의존하는 타입입니다. 예를 들어, Location 타입이 있습니다. 우리는 <T>라는 특정 매개 변수로 이를 매개 변수화합니다.
콤마로 구분하여 많은 타입 매개 변수를 작성합니다. 호출할 때 필요한 값을 추가합니다.
interface Coordinate<Lat, Lng, Title> {
latitude: Lat;
longitude: Lng;
label: Title;
}
const myLocation: Coordinate<number, number, string> = {
latitude: 59.436962,
longitude: 24.753574,
label: 'Tallinn',
};
console.log(myLocation);
TypeScript
복사
Types relationship.For example, with string type, we have such dependency:
unknown (super-type of any type) -> string -> never (sub-type of any type).
contains all possible values -> string -> there will not or should not be a value
We can limit parameters by super-types with help of the keyword “extends”. For example, we use a generic type with 1 parameter. And let it be a sub-type of string.
유형 관계. 예를 들어, 문자열 유형에 대해서는 다음과 같은 종속성이 있습니다.
알 수 없음 (모든 유형의 상위 유형) -> 문자열 -> 절대 없음 (모든 유형의 하위 유형).
모든 가능한 값을 포함 -> 문자열 -> 값이 없거나 없어서는 안 됩니다.
슈퍼 유형을 키워드 "extends"를 사용하여 제한할 수 있습니다. 예를 들어, 1 개의 매개 변수를 가진 일반 유형을 사용합니다. 그리고 문자열의 하위 유형으로 만듭니다.
function getBilliards<T extends string>(s: T): [T] {
return [s];
}
// argument of this type is assignable to parameter of type <...>
const result = getBilliards<'carom' | 'snooker' | 'pool'>('carom');
console.log(result); // ["carom"]
TypeScript
복사
Conditional types — it is like a ternary operator for Types.
Conditional pruning is also useful for narrowing union types. Standard library include the NonNullable<T> type which removes null and undefined from a union type.
type MyNonNullable<T> = T extends null | undefined ? never : T;
const result: MyNonNullable<1> = 1;
console.log(result);
const resultStr: MyNonNullable<'lorem ipsum'> = 'lorem ipsum';
console.log(resultStr);type MyNonNullable<T> = T extends null | undefined ? T : never;
const result3: MyNonNullable<null> = null;
console.log(result3);
TypeScript
복사
“Never”, “unknown” and “any” is significant operators:
1.
“never” used for things that never happen. Especially in conditional types to prune unwanted cases.
2.
“unknown” contains all possible values, but it might have any type.
3.
“any” is the very special case. If you don’t need to check property type or you didn’t know about it (when getting data from a third party library), “any” will not check their type existence.
Generics and never conditional type: infer fetch the type or should not be a value.
제네릭과 조건부 유형: 유형을 추론하거나 값이 되어서는 안 됩니다.
type ListItem<T extends any[]> = T extends (infer X)[] ? X : never;
type stringItems = ListItem<string[]>;const result: stringItems = '1';
const result2: stringItems = 2; //number is not assignable to string
console.log(result);type typesItem = ListItem<[string, number]>;
const different: typesItem = 'Lorem Ipsum';
console.log(different);
const different2: typesItem = 1.618034;
console.log(different2);
TypeScript
복사
1.
Infer
With infer, the compiler ensures that you have declared all type variables explicitly.
type R = { a: number };
type MyType<T> = T extends infer R ? R : never;
// infer new variable R from Ttype T1 = MyType<{b: string}>; // T1 is { b: string; }const result: T1 = {
b: 'Alex'
}
console.log(result); //string
TypeScript
복사
Without infer, the compiler will not know, if you wanted to introduce an additional type variable R that is to be inferred, or if R is just an accidental typing error.
interface Billiard {
pool: string
}type MyBilliard<T> = T extends Billiard2 ? 'snooker' : 'caromball'; // mistake: Billiard2 not declared yettype MyBilliard<T> = T extends infer Billiard ? Billiard : never;
type AnotherBilliard<T> = T extends Billiard ? Billiard : never;type myPool = MyBilliard<{lengthTable: number}>
type myPool2 = AnotherBilliard<{pool: string}> // assign string only// try use properties
const result: myPool = {
lengthTable: 360
}
console.log(result);const result2: myPool2 = {
pool: 'Pyramid'
}
console.log(result2);
TypeScript
복사
Check function type: if the T type extends a function, the ReturnType infers the type of the function result otherwise it returns the never type.
type MyReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never;
function getMobile() {
return {
platform: "default",
version: "1.0",
};
}
type Mobile = MyReturnType<typeof getMobile>;
const result: Mobile = {
platform: "Android",
version: "2.0"
};
console.log(result);
TypeScript
복사
2. Omit
Since 3.5 version of Typescript we have standard Omit keyword. Previous versions allows to achieve Omit behavior with combinations of Pick and Exclude keywords.
Omit<T, K> create a type by picking all properties from T and then removing K. In other words Omit is a generic utility type, that drops keys of T specified in K. Mapping DTO is the usual place to work with Omit.
interface Fish{
property1?: string;
property2?: string;
property3?: string;
}
// omit single property
type Fisher = Omit<Fish, 'property3'>;// omit multiple properties
type Fisher = Omit<Fish, 'property2' | 'property3'>;const result: Fisher = {property2: 'fish'}; //not assignable to typeconst resultWorks: Fisher= {property1: 'fisher'};
console.log(resultWorks);
TypeScript
복사
Another sample with Pick and Omit logic: add new properties any time
interface Fisher {
fullname: string;
email: string;
}
interface FisherNew extends Pick<Fisher, 'email'> { }
type MyFisher = Omit<Fisher, keyof FisherNew> & { // Fisher only
experience: number;
}
const result: MyFisher = {
experience: 10,
fullname: 'Alex',
//email: '' // does not exist in type MyFisher
}
console.log(result);
TypeScript
복사
3. Pick — when need to reuse properties inside a type
Pick saves our times having to create new types just to copy the properties of existing types.
Pick does the opposite of Omit. Pick<T, K> Constructs a type by picking the set of properties K from T.
interface User {
id: number;
name: string;
phone: string;
age: number;
}
type Person = Pick<User, 'id' | 'name'>;const myPerson: Person = {
id: number;
name: string;
phone: string; //error
}// or we can declare type manually without using a Pick keyword
type Person = {
id: number;
name: string;
}
TypeScript
복사
4. Partial — makes all properties of the provided interface optional
It sets all properties of the object to optional and allows you to think about only updated keys.
interface Profile {
id: number,
username: string,
phone: string
}const updateWithPartial: Partial<Profile> = {
username: 'Alex'
};const instance: Profile = {
id: 1,
username: 'my Name',
phone: '(+xxx) xxxx xxx xxx'
};
TypeScript
복사
5. Readonly — works with immutable data.
Keywords like readonly and const is another case for work with properties: readonly used for class properties, const for variables.
Readonly<T> logic is very straightforward: takes the type it receives and marks all of its properties as read only. This will cause compilation errors to be thrown if properties of the returned type are reassigned.
interface Work{
start: Date,
end: Date,
comments: string
status: boolean
}
interface State {
myWork: Readonly<Work>[]
}
const task: Readonly<State> = {
myWork: [
{ status: false, comments: 'What has been done?' },
]
};//task.myWork[0].status = true; // impossible, due to a read-only// ability to change it:
const myNewWork = {
myWork: [
{...task.myWork[0], status: true }, ...task.myWork.slice(1)
]
};
console.log(myNewWork);
TypeScript
복사
6. Record — Very useful when constructing types for configurations.
A Record lets you create a new type from a Union. The values in the Union are used as attributes of the new type.
type SampleProps = Record<'property1' | 'property2', Cat>;// in other words, it is the same as:
type SampleProps = {
property1: Cat;
property2: Cat;
}
TypeScript
복사
Sample with enum:
enum Billiard {
Pool, Snooker, Carom, Pyramid
}
function getBilliardName(c: Billiard): string {
const getNames: Record<Billiard, string> = {
[Billiard.Pool]: 'American Pool',
[Billiard.Snooker]: 'English Snooker',
[Billiard.Carom]: 'French Carom ball',
[Billiard.Pyramid]: 'Russian Pyramid'
}
return getNames[c] || '';
}
getBilliardName('Pyramid');
TypeScript
복사
Another shopping example:
type ProductId = string;
type Status = 'preOrder' | 'inStock' | 'soldOut';
interface Availability {
availability: Status;
amount: number;
};const x: Record<ProductId, Availability> = {
'1001': { availability: 'inStock', amount: 5},
'1002': { availability: 'preOrder', amount: 10 },
'1003': { availability: 'soldOut', amount: 17},
};
console.log(x['1002']);
TypeScript
복사
Conclusion:
Utility types just let to make our code cleaner and readable. It allows to make application more structured, simplify our work and reduce efforts. Sure it is not possible to achieve in JavaScript.