type conversion
1. type inference
- TypeScript can infer the type of variables based on some simple rules
1.1 From right to left
- The type of a variable can be inferred from the definition
- Here is an example of right to left flow type
let foo = 1; // foo 是 'number'
let bar = "hello "; // bar 是 'string'
//foo = bar; // Error: 不能将 'string' 赋值给 `number`1.2 Bottom outflow
- The return type can be inferred by the return statement
function add(a: number, b: number) {
return a + b;
}
let c = add(1, 2);1.3 From left to right
- Function parameter types/return value types can also be inferred through assignment
type Sum = (a: number, b: number) => number;
let sum: Sum = (a, b) => {
a = "hello ";
return a + b;
};1.4 Structure
- The inference rules also apply to the existence of structures (object literals)
const person = {
name: "hello ",
age: 11,
};
let name = person.name;
let age = person.age;
age = "hello"; // Error:不能把 'string' 类型赋值给 'number' 类型1.5 Deconstruction
- The rules of inference also apply to deconstruction
const person = {
name: "hello ",
age: 11,
};
let { name, age } = person;
age = "hello"; // Error:不能把 'string' 类型赋值给 'number' 类型
//数组也一样
const numbers = [1, 2, 3];
numbers[0] = "hello"; // Error:不能把 'string' 类型赋值给 'number' 类型1.5 DefaultProps
interface DefaultProps {
name?: string;
age?: number;
}
let defaultProps: DefaultProps = {
name: "hello ",
age: 10,
};
let props = {
...defaultProps,
home: "北京",
};
type Props = typeof props;1.6 Use return values carefully
- Although TypeScript can generally infer the return value of a function, it may not be what you want.
function addOne(a: any) {
return a + 1;
}
function sum(a: number, b: number) {
return a + addOne(b);
}
type Ret = ReturnType<typeof sum>;1.7 Crossover types
- Intersection Types combine multiple types into one type
- This allows us to stack multiple existing types together into one type that contains all the required characteristics of the type
//TypeScript 交叉类型是将多个类型合并为一个类型
//这让我们可以把现有的多种类型叠加到一起成为一种类型
//它包含了所需的所有类型的特性
export {};
//接口的交叉
interface Bird {
name: string;
fly(): void;
}
interface Person {
name: string;
talk(): void;
}
type BirdPerson = Bird & Person;
let p: BirdPerson = { name: "hello ", fly() {}, talk() {} };
p.fly;
p.name;
p.talk;interface X {
a: string;
b: string;
}
interface Y {
a: number;
c: string;
}
type XY = X & Y;
type YX = Y & X;
//c = string & number
//let p1: XY={a:'',b:'',c:''};Cross type of union type
type Ta = string | number;
type Tb = number | boolean;
type Tc = Ta & Tb;The mixin mixing mode allows you to create a new object from two objects. The new object will have all the functions of the two objects.
interface AnyObject {
[prop: string]: any;
}
function mixin<T extends AnyObject, U extends AnyObject>(
one: T,
two: U,
): T & U {
const result = <T & U>{};
for (let key in one) {
(<T>result)[key] = one[key];
}
for (let key in two) {
(<U>result)[key] = two[key];
}
return result;
}
const x = mixin({ name: "hello " }, { age: 11 });
console.log(x.name, x.age);2. typeof
- You can get the type of a variable
//先定义类型,再定义变量
type People = {
name: string;
age: number;
gender: string;
};
let p1: People = {
name: "hello ",
age: 10,
gender: "male",
};//先定义变量,再定义类型
let p1 = {
name: "hello ",
age: 10,
gender: "male",
};
type People = typeof p1;
function getName(p: People): string {
return p.name;
}
getName(p1);3. index access operator
- You can get the subtype of a type through []
interface Person {
name: string;
age: number;
job: {
name: string;
};
interests: { name: string; level: number }[];
}
let FrontEndJob: Person["job"] = {
name: "前端工程师",
};
let interestLevel: Person["interests"][0]["level"] = 2;4. keyof
- Index type query operator
interface Person {
name: string;
age: number;
gender: "male" | "female";
}
//type PersonKey = 'name'|'age'|'gender';
type PersonKey = keyof Person;
function getValueByKey(p: Person, key: PersonKey) {
return p[key];
}
let val = getValueByKey({ name: "hello ", age: 10, gender: "male" }, "name");
console.log(val);5. Mapping type
- When defining, use the in operator to batch define attributes in the type.
interface Person {
name: string;
age: number;
gender: "male" | "female";
}
//批量把一个接口中的属性都变成可选的
type PartPerson = {
[Key in keyof Person]?: Person[Key];
};
let p1: PartPerson = {};
//也可以使用泛型
type Part<T> = {
[key in keyof T]?: T[key];
};
let p2: Part<Person> = {};Get the array of values through the array of keys
function pick<T, K extends keyof T>(o: T, names: K[]): T[K][] {
return names.map((n) => o[n]);
}
let user = { id: 1, name: "hello " };
type User = typeof user;
const res = pick<User, keyof User>(user, ["id", "name"]);
console.log(res);6. condition type
- Logical branches can be added when defining generics, making generics more flexible in the future.
6.1 Define condition types
interface Fish {
name: string;
}
interface Water {
name: string;
}
interface Bird {
name: string;
}
interface Sky {
name: string;
}
//若 T 能够赋值给 Fish,那么类型是 Water,否则为 Sky
type Condition<T> = T extends Fish ? Water : Sky;
let condition: Condition<Fish> = { name: "水" };6.2 Distribution of condition types
interface Fish {
fish: string;
}
interface Water {
water: string;
}
interface Bird {
bird: string;
}
interface Sky {
sky: string;
}
//naked type
type Condition<T> = T extends Fish ? Water : Sky;
//(Fish extends Fish ? Water : Sky) | (Bird extends Fish ? Water : Sky)
// Water|Sky
let condition1: Condition<Fish | Bird> = { water: "水" };
let condition2: Condition<Fish | Bird> = { sky: "天空" };- Conditional types have a feature, which is "distributed conditional type", but distributed conditional types have prerequisites. The type to be checked in the conditional type must be a naked type parameter.
//none naked type
//type Condition<T> = [T] extends [Fish] ? Water : Sky;Find the part of type T that is not included in U
//never会被自动过滤
type Diff<T, U> = T extends U ? never : T;
type R = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"
type Filter<T, U> = T extends U ? T : never;
type R1 = Filter<string | number | boolean, number>;6.3 Built-in condition types
- TS has built-in some commonly used condition types, which can be viewed in lib.es5.d.ts:
- utility-types
6.3.1 Exclude
- Exclude U from the types to which T is assignable
type Exclude<T, U> = T extends U ? never : T;
type E = Exclude<string | number, string>;
let e: E = 10;6.3.2 Extract
- Extract U from T assignable type
type Extract<T, U> = T extends U ? T : never;
type E = Extract<string | number, string>;
let e: E = "1";6.3.3 NonNullable
- Exclude null and undefined from T
type NonNullable<T> = T extends null | undefined ? never : T;
type E = NonNullable<string | number | null | undefined>;
let e: E = null;6.3.4 ReturnType
- infer first appeared in this PR, indicating the type variable to be inferred in the extends conditional statement
- Get the return type of a function type
export {};
type ReturnType<T extends (...args: any[]) => any> = T extends (
...args: any[]
) => infer R
? R
: any;
function getUserInfo() {
return { name: "hello ", age: 10 };
}
// 通过 ReturnType 将 getUserInfo 的返回值类型赋给了 UserInfo
type UserInfo = ReturnType<typeof getUserInfo>;
const userA: UserInfo = {
name: "hello ",
age: 10,
};6.3.5 Parameters
- Constructs a tuple type of the types of the parameters of a function type T
- Parameters
export {};
type Parameters<T> = T extends (...args: infer R) => any ? R : any;
type T0 = Parameters<() => string>; // []
type T1 = Parameters<(s: string) => void>; // [string]
type T2 = Parameters<<T>(arg: T) => T>; // [unknown]6.3.6 InstanceType
- Get the instance type of the constructor type
- InstanceType
type Constructor = new (...args: any[]) => any;
type ConstructorParameters<T extends Constructor> = T extends new (
...args: infer P
) => any
? P
: never;
type InstanceType<T extends Constructor> = T extends new (
...args: any[]
) => infer R
? R
: any;
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
getName() {
console.log(this.name);
}
}
//构造函数参数
type constructorParameters = ConstructorParameters<typeof Person>;
let params: constructorParameters = ["hello "];
//实例类型
type Instance = InstanceType<typeof Person>;
let instance: Instance = { name: "hello ", getName() {} };6.3.7 infer+distributed
distributive-conditional-types
"Distributive conditional types" are mainly used to split the union type on the left side of extends
"Distributive conditional types" are conditional types composed of "naked type parameters".And "naked type parameter" means a type that has not been Wrapped (such as: Array, [ T ] , Promise, etc. are not "naked type parameters")."Distributive conditional types" are mainly used to split the union type of the left part of extends. For example: in the conditional type T extends U ? X : Y, when T is A | When B, it will be split into A extends U ? X : Y | B extends U ? X : Y;
Taking advantage of the contravariant position, multiple candidate types of the same type of variable will be inferred as intersection types.
tuple to union
type ElementOf<T> = T extends Array<infer E> ? E : never;
type TTuple = [string, number];
type ToUnion = ElementOf<TTuple>; // string | number//联合类型(Union Types)表示取值可以为多种类型中的一种
//交叉类型(Intersection Types)表示将多个类型合并为一个类型
//联合类型转交叉类型
//union 转 intersection
//union 转 intersection 的操作多用于 mixin 中
//https://github.com/Microsoft/TypeScript/issues/27907
type T1 = { name: string };
type T2 = { age: number };
type UnionToIntersection<T> = T extends {
a: (x: infer U) => void;
b: (x: infer U) => void;
}
? U
: never;
type T3 = UnionToIntersection<{ a: (x: T1) => void; b: (x: T2) => void }>; // T1 & T27. Built-in tool types
- There are some tool types built into TS to help us better use the type system
- utility-types
- Added control over mapped type modifiers in TypeScript
- Specifically, a readonly or ? modifier in a mapped type may be prefixed with + or - to indicate that the modifier should be added or removed
Symbol Meaning +? becomes optional -? becomes required
7.1 Partial
- Partial can change the incoming attributes from non-optional to optional. The specific usage is as follows:
type Partial<T> = { [P in keyof T]?: T[P] };
interface A {
a1: string;
a2: number;
a3: boolean;
}
type aPartial = Partial<A>;
const a: aPartial = {}; // 不会报错7.2 Type recursion
interface Company {
id: number;
name: string;
}
interface Person {
id: number;
name: string;
company: Company;
}
type DeepPartial<T> = {
[U in keyof T]?: T[U] extends object ? DeepPartial<T[U]> : T[U];
};
type R2 = DeepPartial<Person>;7.3 Required
- Required can change the optional options in the passed-in attributes into required options. Here, the -? modifier is used to achieve this.
interface Person {
name: string;
age: number;
gender?: "male" | "female";
}
/**
* type Require<T> = { [P in keyof T]-?: T[P] };
*/
let p: Required<Person> = {
name: "hello ",
age: 10,
//gender:'male'
};7.4 Readonly
- Readonly is implemented by adding the readonly modifier to each attribute passed in.
interface Person {
name: string;
age: number;
gender?: "male" | "female";
}
//type Readonly<T> = { readonly [P in keyof T]: T[P] };
let p: Readonly<Person> = {
name: "hello ",
age: 10,
gender: "male",
};
p.age = 11;7.5 Pick
- Pick can help us pick an item from the incoming attributes and return it
interface Animal {
name: string;
age: number;
gender: number;
}
/**
* From T pick a set of properties K
* type Pick<T, K extends keyof T> = { [P in K]: T[P] };
*/
// 摘取 Animal 中的 name 属性
interface Person {
name: string;
age: number;
married: boolean;
}
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result: any = {};
keys.map((key) => {
result[key] = obj[key];
});
return result;
}
let person: Person = { name: "hello ", age: 10, married: true };
let result: Pick<Person, "name" | "age"> = pick<Person, "name" | "age">(
person,
["name", "age"],
);
console.log(result);7.6 Record
- Record is a higher-level type in TypeScript
- It maps all attribute values of one type to another type and creates a new type
/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};function mapObject<K extends string | number, T, U>(
obj: Record<K, T>,
map: (x: T) => U,
): Record<K, U> {
let result: any = {};
for (const key in obj) {
result[key] = map(obj[key]);
}
return result;
}
let names = { 0: "hello", 1: "world" };
let lengths = mapObject<string | number, string, number>(
names,
(s: string) => s.length,
);
console.log(lengths); //{ '0': 5, '1': 5 }type Point = "x" | "y";
type PointList = Record<Point, { value: number }>;
const cars: PointList = {
x: { value: 10 },
y: { value: 20 },
};8. Custom advanced type
8.1 Proxy
type Proxy<T> = {
get(): T;
set(value: T): void;
};
type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>;
};
function proxify<T>(obj: T): Proxify<T> {
let result = {} as Proxify<T>;
for (const key in obj) {
result[key] = {
get: () => obj[key],
set: (value) => (obj[key] = value),
};
}
return result;
}
let props = {
name: "hello ",
age: 10,
};
let proxyProps = proxify(props);
console.log(proxyProps);
function unProxify<T>(t: Proxify<T>): T {
let result = {} as T;
for (const k in t) {
result[k] = t[k].get();
}
return result;
}
let originProps = unProxify(proxyProps);
console.log(originProps);8.2 SetDifference
- SetDifference (same as Exclude)
/**
* SetDifference (same as Exclude)
* @desc Set difference of given union types `A` and `B`
* @example
* // Expect: "1"
* SetDifference<'1' | '2' | '3', '2' | '3' | '4'>;
*
* // Expect: string | number
* SetDifference<string | number | (() => void), Function>;
*/
export type SetDifference<A, B> = A extends B ? never : A;8.3 Omit
- The function of Exclude is to exclude elements from T that are assignable to U.
- The function of Omit<T, K> is to ignore certain attributes in T
- Omit = Exclude + Pick
/**
* Omit (complements Pick)
* @desc From `T` remove a set of properties by key `K`
* @example
* type Props = { name: string; age: number; visible: boolean };
*
* // Expect: { name: string; visible: boolean; }
* type Props = Omit<Props, 'age'>;
*/
export type Omit<T, K extends keyof any> = Pick<T, SetDifference<keyof T, K>>;8.4 Diff
/**
* Diff
* @desc From `T` remove properties that exist in `U`
* @example
* type Props = { name: string; age: number; visible: boolean };
* type DefaultProps = { age: number };
*
* // Expect: { name: string; visible: boolean; }
* type DiffProps = Diff<Props, DefaultProps>;
*/
export type Diff<T extends object, U extends object> = Pick<
T,
SetDifference<keyof T, keyof U>
>;8.5 Intersection
/**
* Intersection
* @desc From `T` pick properties that exist in `U`
* @example
* type Props = { name: string; age: number; visible: boolean };
* type DefaultProps = { age: number };
*
* // Expect: { age: number; }
* type DuplicateProps = Intersection<Props, DefaultProps>;
*/
export type Intersection<T extends object, U extends object> = Pick<
T,
Extract<keyof T, keyof U> & Extract<keyof U, keyof T>
>;8.6 Overwrite
- Overwrite<T, U>, as the name suggests, overwrites the same attributes of T with the attributes of U.
- mapped-types
/**
* Overwrite
* @desc From `U` overwrite properties to `T`
* @example
* type Props = { name: string; age: number; visible: boolean };
* type NewProps = { age: string; other: string };
*
* // Expect: { name: string; age: string; visible: boolean; }
* type ReplacedProps = Overwrite<Props, NewProps>;
*/
export type Overwrite<
T extends object,
U extends object,
I = Diff<T, U> & Intersection<U, T>,
> = Pick<I, keyof I>;
type Props = { name: string; age: number; visible: boolean };
type NewProps = { age: string; other: string };
// Expect: { name: string; age: string; visible: boolean; }
type ReplacedProps = Overwrite<Props, NewProps>;8.7 Merge
- The function of Merge<O1, O2> is to merge the properties of two objects:
- Merge<O1, O2> = Compute + Omit<U, T>
type O1 = {
id: number;
name: string;
};
type O2 = {
id: number;
age: number;
};
//Compute的作用是将交叉类型合并
type Compute<A extends any> = A extends Function ? A : { [K in keyof A]: A[K] };
type R1 = Compute<{ x: "x" } & { y: "y" }>;
type Merge<O1 extends object, O2 extends object> = Compute<
O1 & Omit<O2, keyof O1>
>;
type R2 = Merge<O1, O2>;8.8 Mutable
- Remove readonly from all properties of T
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};9. Comprehensive actual combat
- The infer keyword declares a type variable. The type will be inferred when the type system provides sufficient conditions.
- typescript_zh
- codesandbox
interface Action<T> {
payload?: T;
type: string;
}
class EffectModule {
count = 1;
message = "hello!";
delay(input: Promise<number>): Promise<Action<string>> {
let action: Promise<Action<string>> = input.then((i) => ({
payload: `hello ${i}!`,
type: "delay",
}));
return action;
}
setMessage(action: Action<Date>): Action<number> {
let action2: Action<number> = {
payload: action.payload!.getMilliseconds(),
type: "set-message",
};
return action2;
}
}
//把 EffectModule 中的方法名取出来
type methodsPick<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
//定义转换前后的方法
type asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>>; // 转换前
type asyncMethodConnect<T, U> = (input: T) => Action<U>; // 转换后
type syncMethod<T, U> = (action: Action<T>) => Action<U>; // 转换前
type syncMethodConnect<T, U> = (action: T) => Action<U>; // 转换后
//条件类型+推断类型
type EffectModuleMethodsConnect<T> =
T extends asyncMethod<infer U, infer V>
? asyncMethodConnect<U, V>
: T extends syncMethod<infer U, infer V>
? syncMethodConnect<U, V>
: never;
type EffectModuleMethods = methodsPick<EffectModule>;
//映射类型
type Connect = (module: EffectModule) => {
[M in EffectModuleMethods]: EffectModuleMethodsConnect<EffectModule[M]>;
};
type Connected = {
delay(input: number): Action<string>;
setMessage(action: Date): Action<number>;
};
const connect: Connect = (m: EffectModule): Connected => ({
delay: (input: number) => ({
type: "delay",
payload: `hello 2`,
}),
setMessage: (input: Date) => ({
type: "set-message",
payload: input.getMilliseconds(),
}),
});
export const connected: Connected = connect(new EffectModule());