1, Type inference
In the previous learning process, we need to write type annotations when defining basic types of variables. Although this can clearly indicate the data type of variables, it is really troublesome to write. TS also helps us take this problem into consideration. In most cases, TS will automatically deduce the type of variable according to the context and assignment expression without specifying the type annotation. This ability is called type inference:
let str = 'this is string'; let num = 1; let bool = true; // Equivalent to let str: string = 'this is string'; let num: number = 1; let bool: boolean = true;
Except for variables with initial assignment, function parameters with default values and return types of functions can be inferred:
/** According to the type of the parameter, it is inferred that the type of the return value is also number */ function add1(a: number, b: number) { return a + b; } const x1= add1(1, 1); // It is inferred that the type of x1 is also number /** Infer that the type of parameter b is number, and the type of return value is also number */ function add2(a: number, b = 1) { return a + b; }
If the variable is declared without simultaneous assignment, no matter what type of value is assigned later, it will be inferred as any type and can receive any type of value:
// Unassigned when declared. Type is any let myFavoriteNumber; myFavoriteNumber = 'seven'; myFavoriteNumber = 7;
2, Type assertion
1. Concept
Type assertion means that in some cases, we are very clear about the variable type of a value, but from the type inference of TS, there may be multiple data types. At this time, we can forcibly specify the variable type of the value through type assertion, so that the value can be assigned to the variable of the corresponding type, for example:
// An array was initialized const arrayNumber: number[] = [1, 2, 3, 4]; // We clearly know that there are numbers greater than 3, so the return value must be number // However, in TS's view, the return value may be number or undefined, so assigning directly to the number variable will report an error const greaterThan2: number = arrayNumber.find(num => num > 2); // Prompt ts(2322) // Add a type assertion to force the data type of the return value const greaterThan2: number = arrayNumber.find(num => num > 2) as number;
There are two types of syntax for type assertion: < data type > data or data as data type, which have the same function, but angle brackets will cause syntax conflict in JSX of React. It is recommended to use as syntax:
let someValue: any = "this is a string"; // Angle bracket syntax let strLength: number = (<string>someValue).length; // as grammar let strLength: number = (someValue as string).length;
2. Non null assertion
We can use the suffix expression operator! To assert that a value is a type that is neither null nor undefined. In short, it is to exclude null and undefined from the value field of a value:
// Define a variable so that its type is union type let may: null | undefined | string; // Use non null assertions, excluding null and undefined, so it can only be string, so toString() can be used may!.toString(); // ok // Non null assertion values that are not used may be of multiple types. toString() cannot be used directly may.toString(); // Error ts(2531)
3. Determine assignment assertion
In TS, if a variable is used when it is declared but not assigned, an error will be compiled. However, if we are very clear that this variable will be assigned, we can use the definite assignment assertion, that is, when declaring the variable, add one after the variable!, The TS compiler will no longer report errors:
// Declare a variable without assigning a value. Use the deterministic assignment assertion let x!: number; // Assign values in this method initialize(); // However, the compiler does not know that it will assign a value. If it does not add a definite assignment assertion, an error will be reported console.log(2 * x); // Ok function initialize() { x = 10; }
3, Literal type
1. Concept
In TS, literal quantity can represent not only value, but also type, that is, literal type. That is, when declaring a variable, assign a value to the variable and set the type of the variable as a type. At present, three literal types are supported: string type, numeric type and boolean type:
// String literal type let specifiedStr: 'this is string' = 'this is string'; // Numeric literal type let specifiedNum: 1 = 1; // Boolean literal type let specifiedBoolean: true = true;
In addition to the literal value of 'string' in the case of 'string', the literal value of 'string' in the above case is not equivalent to that of 'string':
// String literal type let specifiedStr: 'this is string' = 'this is string'; // string type let str: string = 'any string'; // Type 'string' cannot be assigned to type 'this is string' specifiedStr = str; // ts(2322) // Type 'this is string' can be assigned to type 'string' str = specifiedStr; // ok
Numeric literal types are similar to Boolean literal types.
2. String literal type
In practical application, defining a single string literal type is not very useful. We usually form multiple literal types into a joint type to explicitly limit the value range of variables. When used in combination with a function, the parameter is limited to the specified literal type collection. During compilation, it will check whether the parameter is a member of the specified union type:
// Joint literal type type Direction = 'up' | 'down'; // Limit the parameter type of the function function move(dir: Direction) { // ... } // Can only be up or ok move('up'); // ok // If it is another string, it will compile and report an error move('right'); // ts(2345)
3. Numeric literal type and Boolean literal type
The usage is similar to that of string literal type. It is to more clearly limit the value range of variables, so that users must use the data of specific values:
interface Config { size: 'small' | 'big'; isEnable: true | false; margin: 0 | 2 | 4; }
4, Type widening
1. Concept
If the variables defined by let and var, the formal parameters of functions and the non read-only attributes of objects meet the conditions of type inference, the inferred type is the parent type after the widening of the literal type of the initial value. Since const is a constant and cannot be changed, the type will not be widened:
let str = 'this is string'; // The type is broadened to string let strFun = (str = 'this is string') => str; // The type is broadened to (STR?: String) = > string; const specifiedStr = 'this is string'; // The declared constant type is' this is string ' let str2 = specifiedStr; // The type is' string '
2. Restriction type widening
We can add the type annotation of the corresponding literal type to the variable to limit the type broadening:
// Add type annotation to restrict type widening. The type is' this is string ' const specifiedStr: 'this is string' = 'this is string'; // Even if the let definition is used, the type is' this is string ' let str2 = specifiedStr;
3. Special type widening
In TS, if the variable defined through let and var is not marked with type annotation and assigned null or undefined, the type of this variable will be widened to any:
let x = null; // Type broadened to any let y = undefined; // Type broadened to any const z = null; // Constant type cannot be null let z2 = z; // The type is null let x2 = x; // The type is null let y2 = y; // The type is undefined
Note: in strict mode, in some older versions (2.0), null and undefined will not be widened to "any".
4. Type of object property
TS's widening algorithm will regard the internal attribute of the object as the variable assigned to the let keyword declaration, and then infer the type of its attribute. It also prevents us from adding properties to the object that do not exist at the time of the Declaration:
// Declaration object const obj = { x: 1, // Type is expanded to number type }; // Assign a value of type number obj.x = 6; // Assigning a string type will result in an error obj.x = '6'; // Type '"6"' is not assignable to type 'number'.obj.x = '6'; // Add new attribute obj.y = 8; // Property 'y' does not exist on type '{ x: number; }'.obj.y = 8
5, Type narrowing
1. Concept
In TS, the process of reducing the type of variables from a relatively broad set to a clear set by some means is type reduction.
2. Type guard
We can use the type guard combined with typeof to reduce the type of function parameters:
let func = (anything: any) => { // If the passed parameter is of string type, it will be reduced to string type when returned if (typeof anything === 'string') { return anything; // The type is string // If the passed parameter is of type number, it will be reduced to type number when returned } else if (typeof anything === 'number') { return anything; // The type is number } return null; };
When using type guard, you need to pay attention to some special values. For example, the result of typeof null is object.
3. Process control + equivalence judgment
We can also reduce the union type through process control statements (including but not limited to if, ternary operator, switch branch, etc.) + equivalence judgment (= = =):
// Define a union type type Goods = 'pen' | 'pencil' |'ruler'; // Parameter is union type const getCost = (item: Goods) => { // Type reduction using equivalent judgment if (item === 'pen') { return item; // The item type is reduced to 'pen' } else if (item === 'pencil') { return item; // The item type is reduced to 'pencil' } else { return item; // The item type is reduced to 'ruler' } }
4. Label Union
We can also define a type attribute for the type and narrow the type through the type attribute:
// Interface type (the next blog will talk about it) interface UploadEvent { // Define type type: "upload"; filename: string; contents: string; } // Interface type (the next blog will talk about it) interface DownloadEvent { type: "download"; filename: string; } type AppEvent = UploadEvent | DownloadEvent; function handleEvent(e: AppEvent) { switch (e.type) { case "download": e; // Type is DownloadEvent break; case "upload": e; // Type is UploadEvent break; } }
6, Cross type
Cross type refers to the use of & to merge multiple types into one type that contains all the properties of the merged type. Obviously, if the original type and literal type are combined into a cross type, it makes no sense, because there is no data that can belong to multiple original types at the same time. The real use of cross type is to combine multiple interface types (which will be discussed in the next blog) into one type, so as to achieve an effect similar to interface inheritance:
// Merging the two object types makes the IntersectionType have id, name and age attributes at the same time type IntersectionType = { id: number; name: string; } & { age: number }; // Type comments must be followed by id, name and age attributes const mixed: IntersectionType = { id: 1, name: 'name', age: 18 }
Cross type is equivalent to finding the union of types, but what happens if two interfaces have properties with the same name? There are many situations here: two attributes with the same name have the same type, two attributes with the same name have different and incompatible types, and two attributes with the same name have different types, but one is another subtype.
In the first case, if two attributes with the same name have the same type, it will not have much impact. Two attributes with the same name will be combined into one:
// Define a cross type type IntersectionTypeConfict = { id: number; name: string; } & { age: number; name: string; }; // After crossing, it is equivalent to {id: number; name: string; age: number}
When two attributes with the same name have different and incompatible types, for example, one is string and the other is number, the defined cross type is a useless type, because there can be no data belonging to both string and number, and the attributes with the same name of this cross type will become never type:
// Define a cross type. Because there are properties with the same name and different types are incompatible, the properties with the same name will become the never type type IntersectionTypeConfict = { id: number; name: string; } & { age: number; name: number; }; const mixedConflict: IntersectionTypeConfict = { id: 1, name: 2, // ts(2322) error, 'number' type cannot be assigned to 'never' type age: 2 };
If the types of attributes with the same name are compatible, for example, one is number, the other is numeric literal type or the subtype of number, the attribute of the combined cross type will become a subtype with a small range:
// Define a cross type. Because there are properties with the same name and different types, but they are compatible, the property will become a subtype type IntersectionTypeConfict = { id: number; name: 2; } & { age: number; name: number; }; let mixedConflict: IntersectionTypeConfict = { id: 1, name: 2, // ok age: 2 }; mixedConflict = { id: 1, name: 22, // '22' type cannot be assigned to '2' type age: 2 };
Another special case is that the attribute with the same name is a complex data type, so the result of intersection is to merge the object members. The merging rules are the same as the type merging:
interface A { x:{d:true}, } interface B { x:{e:string}, } interface C { x:{f:number}, } // After merging, the type becomes {x: {d: true,e: string,f: number}} type ABC = A & B & C let abc:ABC = { x:{ d:true, e:'', f:666 } }