The basics of TypeScript
Github address for basic Notes: github.com/qiqihaobenb… , can be watch, can also be star.
Type Precautions
An array type
There are two types of annotations, especially the second one that uses the Array generic interface built into TS.
let arr1: number[] = [1.2.3]
// This is implemented using the Array generic interface built into TS
let arr2: Array<number | string> = [1.2.3."abc"]
Copy the code
A tuple type
A tuple is a special array that limits the number and type of its elements
let tuple: [number.string] = [0."1"];
Copy the code
Note that tuples are out of bounds. Although elements can be added out of bounds, they are still not accessible out of bounds and are strongly discouraged
tuple.push(2) / / is not an error
console.log(tuple) // [0, "1", 2] can also be printed
console.log(tuple[2]) // If you want to retrieve an out-of-bounds element from a tuple, you will get an error with tuple length 2 and no element at index 2
Copy the code
Function types
Function types can be defined before they are used and implemented without specifying parameter and return value types, and parameter names do not have to be the same as they are defined.
let compute: (x: number, y: number) = > number;
compute = (a, b) = > a + b;
Copy the code
Object type
If you want to assign or modify an attribute value to an object, you can’t use a simple object type. You need to define a full object type
let obj: object = { x: 1, y: 2 };
obj.x = 3; // This is an error. It is simply an object, but what attributes are not specified
// Change the object type definition to the following
let obj: { x: number; y: number } = { x: 1, y: 2 };
obj.x = 3;
Copy the code
Symbol type
The symbol type can be declared or assigned directly. As in ES6, two separately declared symbols are not equal.
let s1: symbol = Symbol();
let s2 = Symbol();
console.log(s1 === s2) // false
Copy the code
The value is undefined or null
Variables can be declared as undefined and NULL, but once declared, no other types can be assigned.
let un: undefined = undefined;
let nu: null = null;
un = 1 / / complains
nu = 1 / / complains
Copy the code
Undefined and NULL are subtypes of any type, which can be assigned to other types. StrictNullChecks: false
// set strictNullChecks: false
let num: number = 123;
num = undefined;
num = null;
// However, it is recommended to set num to the union type
let num: number | undefined | null = 123;
num = undefined;
num = null;
Copy the code
Enumerated type
Enumerations include numeric enumerations, string enumerations, and heterogeneous enumerations (not recommended)
Digital enumeration
Enumerations can be evaluated either by name or by index, so let’s see how we get that.
enum Role {
Reporter = 1,
Developer,
Maintainer,
Owner,
Guest
}
Role.Reporter = 2 // Enumerators are read-only and cannot be modified or reassigned
console.log(Role)
// Print out: {1: "Reporter", 2: "Developer", 3: "Maintainer", 4: "Owner", 5: "Guest", Reporter: 1, Developer: 2, Maintainer: 3, Owner: 4, Guest: 5}
// Enumerations can be evaluated by name as well as by index
// See how the TS compiler implements enumeration using reverse mapping.
"use strict";
var Role;
(function (Role) {
Role[Role["Reporter"] = 1] = "Reporter";
Role[Role["Developer"] = 2] = "Developer";
Role[Role["Maintainer"] = 3] = "Maintainer";
Role[Role["Owner"] = 4] = "Owner";
Role[Role["Guest"] = 5] = "Guest";
})(Role || (Role = {}));
Copy the code
String enumeration
String enumerations can be specified only by name, not by index.
enum Message {
Success = 'success',
Fail = 'failure'
}
console.log(Message)
// Print out: {Success: "Success ", Fail:" failure "}
// We see only the name as the key, indicating that string enumerations cannot be mapped backwards
Copy the code
Constant enumeration
Enumerations declared with const are constant enumerations and are removed at compile time. Month does not generate code after compilation, and can only be used before compilation. Constant enumerations can be used when we do not need an object, but need the value of an object, thus reducing compiled code.
const enum Month {
Jan,
Feb,
Mar
}
let month = [Month.Jan, Month.Feb, Month.Mar];
Copy the code
Heterogeneous enumeration
Numeric and string enumerations are not recommended
enum Answer {
N,
Y = 'Yes'.// C, // The enumerator after the string enumerator must be assigned an initial value
// X = math.random () // Computed values are not allowed in enumerations with string members
}
Copy the code
Enumerators note
- Enumerators are read-only and cannot be modified or reassigned
- Enumeration members are divided into const member and computer member
- Const members, including cases with no initial value, references to existing enumerated members, and constant expressions, are evaluated at compile time and appear as constants in the runtime environment
- Computer members, enumerators that need to be counted, are not counted at compile time and are reserved for the execution of the program
- For enumerators after computed Member, you must assign an initial value or an error will be reported
- Computed values (Computer members) are not allowed in enumerations containing string members, and enumerators after string enumerators must be assigned an initial value or an error will be reported (see heterogeneous types above)
- In a numeric enumeration, if two members have the same index, the subsequent index overwrites the previous one (see enumeration number below).
// enumerator
enum Char {
// const member constant enumeration, which is evaluated at compile time and appears as a constant in the runtime environment
a,
b = Char.a,
c = 1 + 3.// Enumerators that need to be computed in computed Member are not computed at compile time and are kept until execution
d = Math.random(),
e = '123'.length,
// For enumerators behind computed Member, you must assign an initial value or an error will be reported
f = 1
}
console.log(Char)
/ / the enumeration number
enum number { a = 1, b = 5, c = 4, d }
console.log(number) / / print out the {1: "a", 4: "c", 5: a "d", a: 1, b: 5, c: 4, d: 5}
// b = 5, c = 4, d = 5, d = 5, d = 5, d = 5
Copy the code
Enumerations and enumerators as separate types
There are three cases where (1) none of the enumerators have initial values, (2) all of the enumerators are numeric enumerators, and (3) all of the enumerators are string enumerators
- Variables are defined as numeric enumerations. Assigning any number (beyond the number defined by the enumeration) is allowed. Enumerations are unaffected, but strings cannot be assigned, etc.
- Different enumeration types cannot be compared, but the same enumeration type can be compared, but different enumerators of the same enumeration type cannot be compared
- A variable is defined as an enumerated type, and even if it is defined as the type of a specific member of the enumerated type, assignment has no effect on the enumeration. (As follows, E and F remain the same)
- An assignment of a string enumeration type can only use enumerators, not arbitrary assignments. (If down F)
enum E { a, b } // Enumerators have no initial values
enum F { a = 1, b = 5, c = 4, d } // Enumerators are numeric enumerators
enum G { a = 'apple', b = 'banana' } // Enumerators are string enumerations
// A variable is defined as a numeric enumeration type. Assigning any number is allowed. Enumerations are not affected, but strings cannot be assigned.
let e: E = 3
let f: F = 3
// E === f // Different enumeration types cannot be compared and will report an error
console.log(E,F,e,f) / / print: {0: "a", 1: "b", a: 0, b: 1}, {1: "a", 4: "c", 5: a "d", a: 1, b: 5, c: 4, d: 5}, 3, 3
// The visible variables are defined as E,F assignments, and have no effect on the E,F enumeration itself
let e1: E = 3
let e2: E = 3
console.log(e1 === e2) // The same enumeration type can be compared and the result is true
let e3: E.a = 3
let e4: E.b = 3
// e3 === e4 // Different enumerators of the same enumeration type cannot be compared and an error will be reported
console.log(E,E.a,E.b,e3,e4) // Print: {0: "a", 1: "b", a: 0, b: 1} 0 1 3 3, visible variables are defined as E.a,E.b assignment,E and the E.a,E.b enumeration itself has no effect
// String enumeration type assignment, can only use enumeration members, not arbitrary assignment.
let g1: G = 'abc' / / complains
let g2: G = G.a // g2 can be assigned G.a or G.b
let g3: G.a = G.a // g2 can only be assigned G.a
Copy the code
The interface type
Interfaces constrain the structure of objects, functions, and classes
Object type interface
Object redundant fields
The object type interface will report an error when directly validating object literals with redundant fields, which are sometimes unavoidable.
interface List {
id: number;
name: string;
}
interface Result {
data: List[];
}
function render(result: Result) {
result.data.forEach((value) = > {
console.log(value.id,value.name)
})
}
render({
data: [
{id: 1, name: 'A',sex: 'male'},
{id: 2,name: 'B'}}]);// This is the object type interface that validates "object literals" with redundant fields, so the object can only specify known attributes, and "sex" is not in the type "List"
Copy the code
Solution 1: Declare the result variable outside and pass it into the render function instead of passing in the object literal.
// Assign the literal to a variable first to bypass detection
let result = {
data: [
{id: 1, name: 'A',sex: 'male'},
{id: 2,name: 'B'}
]
}
render(result);
Copy the code
As unknown as XXX = as unknown as XXX = as unknown as XXX
render({
data: [{ id: 1, name: "A", sex: "male" }, { id: 2, name: "B"}}]as Result);
// However, if none of the object literals passed in match, type assertion will still report an error
render({
data: [{ id: 1, name: "A", sex: "male"}}]as Result); // The type of the property "data" is incompatible
// As unknown as XXX
render({
data: [{ id: 1, name: "A", sex: "male"}}]as unknown as Result);
Copy the code
Solution 3: Use string index signature
interface List {
id: number;
name: string;
[x:string] :any;
}
Object literals can contain any number of string attributes.
Copy the code
Interface properties can be defined as read-only and optional properties
interface List {
readonly id: number; // Read-only attribute
name: string; age? :number; // Optional attribute
}
Copy the code
Indexable type
Indexable types can be used when you are not sure how many attributes are in an interface. It can be divided into digital index signature and string index signature. If the interface defines a value type of index signature, the value of the attribute to be defined later must be a subtype of the type of signature value. Both types of indexes can be used, but the return value of the numeric index must be a subtype of the return value type of the string index.
interface Names {
[x: string] :number | string;
// y: boolean; / / complains Boolean not assigned to string index type, because of the type of string index sign is number | string, so after the definition of attribute must be signed a subtype of a value type
[z: number] :number; // The numeric index signature can also be defined after the string index signature. The return value of the numeric index must be a subtype of the string index return value type
}
Copy the code
Function type interface
interface Add {
(x: number, y: number) :number;
}
Let Add: (a: number, b: number) => number
let add4: Add = (a,b) = > a + b
Copy the code
Mixed interface
Hybrid interface, notice that there is no order in the attributes of the interface, hybrid interface does not need the first attribute to be an anonymous function.
interface Lib {
version: string; () :void;
doSomething():void;
}
// Type assertion is required
let lib: Lib = (() = > {}) as Lib;
lib.version = '1.0'
lib.doSomething = (a)= > {}
Copy the code
Interface inheritance
// The following is an example of interface inheritance
interface Human {
name: string;
eat(): void;
}
interface Man extends Human {
run(): void
}
interface Child {
cry():void
}
interface Boy extends Man, Child {}
let boy: Boy = {
name: ' ',
run(){},
eat(){},
cry(){}
}
Copy the code
Function type correlation
There are four ways to define TS functions. The first method can be called directly, but the last three methods need to implement the function defined before calling it
// The first is to declare directly
function add1 (x:number, y:number) :number {
return x + y
}
// When applied, the parameter corresponds to the argument
add1(1.2)
// The second variable declaration
let add2: (x:number, y:number) = > number
// Apply as follows
add2 = (a, b) = > a + b
add2(2.2)
// Third type alias
type Add3 = (x: number, y: number) = > number
// Apply as follows
let add3: Add3 = (a, b) = > a + b
add3(3.2)
// The fourth interface implementation
interface Add4 {
(x: number, y: number) :number;
}
Let Add4: (a: number, b: number) => number
let add4: Add4 = (a,b) = > a + b
add4(4.2)
Copy the code
Optional parameters
Optional parameters must be placed after mandatory parameters. Optional parameters cannot be followed by mandatory parameters
// y cannot be followed by mandatory parameters, so d will report an error
// function add5(x:number, y? :number, d:number) {
// This is correct
function add5(x:number, y? :number) {
return y? y + x: x
}
add5(1)
Copy the code
Parameter Default Value
A parameter with a default value does not need to come after a mandatory parameter, but if a parameter with a default value comes before a mandatory parameter, you must explicitly pass undefined to get the default value. Arguments with default values after all required arguments are optional and, like optional arguments, can be omitted when calling a function.
function add6 (x: number, y = 0, z:number,q = 1) {
return x +y + z +q
}
// The second argument must be passed undefined placeholder
add6(1.undefined.2)
Copy the code
Function overloading
A series of function declarations are required to implement overloading in the broadest version of the type. The TS compiler’s function overloading looks up a list of overloads, matches the first one, and executes if the match is successful. So we’re going to put the definition of a high probability match up front.
Declarations of function overloading are used only in the type checking phase and are removed after compilation.
function add8(... rest: number[]):number function add8(... rest: string[]):string function add8(... rest: any[]):any { let first = rest[0] if(typeof first === 'string') { return rest.join('') } if(typeof first === 'number') { Return the rest. The reduce ((pre, cur) = > pre + cur)}} add8 (1, 2, 3) / / 6 add8 (' 1 ', '2', '3') / / '123'Copy the code
class
Class attributes and method notes
- Class properties are instance properties, not stereotype properties, whereas class methods are stereotype methods
- Instance attributes must have initial values or be initialized in a constructor, except for those of type any.
Class inheritance
The constructor of a derived class must contain a “super” call, and” super” must be called before accessing this in the constructor of a derived class.
Class modifier
1. Public: visible to all (default).
2. Private: Private attributes
Private attributes can only be accessed in the declared class, not in the subclass or in the generated instance. Private attributes can be accessed in the instance method, because they are also equivalent to being accessed in the class, but not in the instance method of the subclass.
Class constructor can be defined as a private type, and the class can neither be instantiated nor inherited
3. Protected Properties
Protected properties can only be accessed in the declared class and its subclasses, but protected properties can be accessed in the instance methods because they are also accessed in the class
Class constructor can be defined as a protected type, so the class cannot be instantiated, but can be inherited, like the base class
Readonly Indicates the read-only property
A read-only property must have an initial value or be initialized in a constructor and cannot be changed, and a read-only property that has already been initialized can be re-initialized in a constructor. But cannot be reinitialized in the constructor of its subclass.
5. Static Static attributes
It can only be called by the class name and cannot be accessed from instances and constructors or from constructors and instances in subclasses, but static properties are inheritable and can be accessed by the class name of the subclass
Note: Constructor parameters can also be added with modifiers, which can define the parameters directly as properties of the class
class Dog {
constructor(name: string) {
this.name = name
this.legs = 4 // Read-only attributes that already have default values can be reinitialized
}
public name: string
run() { }
private pri() { }
protected pro() { }
readonly legs: number = 3
static food: string = 'bones'
}
let dog = new Dog('jinmao')
// dog.pri() // Private attributes cannot be called in the instance
// dog.pro() // Protected property that cannot be called in an instance
console.log(Dog.food) // 'bones'
class Husky extends Dog {
constructor(name: string.public color: string) {
super(name)
this.color = color
// this.legs = 5 // The constructor of a subclass cannot reinitialize read-only properties of its parent class
// this.pri() // Subclasses cannot call private attributes of their parent class
this.pro() // Subclasses can call protected properties of their parent class
}
protected age: number = 3
private nickname: string = 'two ha'
info(): string {
return this.age + this.nickname
}
// color: string // color: string //
}
let husky = new Husky('husky'.'black')
husky.info() // This is not reported if the method of the calling class has access to private and protected properties of the class.
console.log(Husky.food) // The 'bones' subclass can call static properties of its parent class
Copy the code
An abstract class
A class that can only be inherited but cannot be instantiated.
Common methods can be added to an abstract class, or abstract methods can be added, and then implemented concretely by subclasses
abstract class Animal {
eat() {
console.log('eat')}abstract sleep(): void // Abstract methods implemented in subclasses
}
// let animal = new animal (
class Cat extends Animal {
constructor(public name: string) {
super()
}
run() { }
// Abstract methods must be implemented
sleep() {
console.log('sleep')}}let cat = new Cat('jiafei')
cat.eat()
Copy the code
Interface class
- When a class implements an interface, it must implement all of the attributes of the interface, though classes can define their own attributes
- Interfaces cannot constrain class constructors, only public members
interface Human {
// new (name:string):void // The interface cannot constrain class constructors
name: string;
eat(): void;
}
class Asian implements Human {
constructor (name: string) {
this.name = name
}
name: string
// Private name: string // An error occurs when an interface uses a private attribute
eat() {}
sleep(){}
}
Copy the code
Interface inheritance class
Equivalent to the class member abstract out, only the class member structure, but no concrete implementation
When the interface removes a class member, it removes not only public attributes, but also private and protected attributes, so non-inherited subclasses will report an error
A subclass of an abstracted class can also implement the interface that the class abstracts from without implementing the properties of the subclass’s parent class
class Auto {
state = 1
// protected state2 = 0 // C is not a subclass of Auto. C implements the interface abstracted from Auto
}
interface AutoInterface extends Auto {
}
class C implements AutoInterface {
state = 1
}
// A subclass of an abstract class can also implement the interface abstracted from the class without implementing the existing attributes of the parent class
class Bus extends Auto implements AutoInterface {
// Don't set state, Bus already has a parent class.
}
Copy the code
The generic
Generic function
Note: the position of function types defined with generics is not used to determine whether parameter types need to be specified, as shown in the following example.
Examples of generic functions
function log<T> (value: T) :T {
console.log(value)
return value
}
log<string[] > ['a'.'b'])
log([1.2]) // You don't need to specify the type, TS will automatically infer
// You can also define generic functions with type aliases
// The following definitions do not specify parameter types
type Log = <T>(value:T) = > T // Do not specify the type of the argument
let myLog: Log = log
// The following definitions must specify parameter types
type Log<T> = (value:T) = > T If you use generics to define function types in this way, you must specify a parameter type
let myLog: Log<string> = log
Copy the code
A generic interface
function log<T> (value: T) :T {
console.log(value)
return value
}
// Only one generic function in the generic interface is constrained below to implement parameter types that do not specify generics
interface Log {
<T>(value: T): T;
}
let myLog: Log = log
The following constrains the entire generic interface. The implementation needs to specify the parameter type of the generic type, or use the generic type with the default type
interface Log1<T> {
(value: T): T;
}
let myLog1: Log1<string> = log
interface Log2<T = string> {
(value: T): T
}
let myLog2: Log2 = log
Copy the code
Note: When a generic interface’s generics are defined as global, the implementation must either specify a parameter type or use a generic with a default type
A generic class
class Log3<T> {
Static members cannot refer to class generic parameters
// static start(value: T) {
// console.log(value)
// }
run(value: T) {
console.log(value)
return value
}
}
let log3 = new Log3<number>()
log3.run(1)
// You can pass in any type without specifying a type
let log4 = new Log3()
log4.run('abc')
Copy the code
Note: Generics cannot be applied to static members of a class. When instantiated, any type can be passed without specifying a type
Generic constraint
Constrain the type passed in by the generic
interface Length {
length: number
}
function log5<T extends Length> (value: T) {
// To print the length attribute of the value defined by the generic T, T must have the length attribute, so the generic constraint is required
console.log(value,value.length)
return value
}
log5([1])
log5('abc')
log5({length: 1})
Copy the code
Generic summary
- With generics, functions and classes can easily support multiple types, increasing the extensibility of programs
- No need to write multiple function overloading, lengthy joint type declaration, enhance code readability
- Flexible control of constraints between types
Type checking mechanism
Type checking mechanism: The principles and behaviors of TypeScript compiler type checking. Its function is to assist development and improve development efficiency
Type inference
Type inference: This is when you don’t need to specify the type of a variable (the return value type of a function). TypeScript can automatically infer a type for a variable based on certain rules
Basic type inference
let a = 1 // Deduce the number
let b = [1] // infer number[]
let c = (x = 1) = > x + 1 // Infer (x? : number) => number
Copy the code
Best generic type inference
When a type needs to be inferred from multiple types, TypeScript does its best to infer a common type that is compatible with all current types
let d = [1.null]
/ / that is one of the most compatible type, so that for (number | null) []
StrictNullChecks: null is a subtype of number. StrictNullChecks: number[]
Copy the code
Context type inference
All of the above inferences are from right to left, that is, inferences from expressions, and context type inferences are from left to right, which usually occur in event processing.
Types of assertions
When you’re sure you know a type better than TS, you can use type assertions to bypass TS’s checks. Retrofitting old code is effective, but prevents abuse.
interface Bar {
bar: number
}
let foo = {} as Bar
foo.bar = 1
// However, the recommendation variable declaration should specify the type
let foo1: Bar = {
bar: 1
}
Copy the code
Type is compatible
When one type Y can be assigned to another type X, we can say that type X is compatible with type Y
X compatible Y: X (target type) = Y (source type)
let s: string = 'a'
s = null StrictNullChecks is set to false. The strictNullChecks type is null-compatible (null is a subtype of a character).
Copy the code
The interface is compatible with
Fewer members compatible with more members
interface X {
a: any;
b: any;
}
interface Y {
a: any;
b: any;
c: any;
}
let x: X = { a: 1, b: 2 }
let y: Y = { a: 1, b: 2, c: 3 }
// The source type can be assigned as long as it has the necessary attributes of the target type. Interfaces are compatible with each other. Interfaces with fewer members are compatible with more members.
x = y
// y = x // incompatible
Copy the code
Function compatibility
type Handler = (a: number, b: number) = > void
function test(handler: Handler) {
return handler
}
Copy the code
1. Parameter number
Fixed parameters
The target function must have more arguments than the source function
Handler Object function. The parameter function passed to test is the source function
let handler1 = (a: number) = > { }
test(handler1) // The function passed in can take a single argument, which is compatible with number
let handler2 = (a: number, b: number, c: number) = > { }
test(handler2) // The function passed can take three arguments (too many arguments), and the argument is number, which is incompatible
Copy the code
Optional and remaining parameters
let a1 = (p1: number, p2: number) = >{}let b1 = (p1? :number, p2? :number) = >{}let c1 = (. args:number[]) = >{}Copy the code
(1) Fixed parameters can be compatible with optional parameters and residual parameters
a1 = b1 / / compatible
a1 = c1 / / compatible
Copy the code
(2) Optional parameters are incompatible with fixed and residual parameters, but can be set to “strictFunctionTypes”: false to eliminate errors and achieve compatibility
b1 = a1 / / not compatible
b1 = c1 / / not compatible
Copy the code
(3) The remaining parameters can be compatible with fixed and optional parameters
c1 = a1 / / compatible
c1 = b1 / / compatible
Copy the code
2. Parameter types
The base type
// Connect to the test function above
let handler3 = (a: string) = > { }
test(handler3) // Type incompatible
Copy the code
The interface type
Interface members more compatible with fewer members, it can also be understood to expand the interface, more parameters compatible with fewer parameters. For incompatible types, you can also set “strictFunctionTypes”: false to eliminate errors and achieve compatibility
interface Point3D {
x: number;
y: number;
z: number;
}
interface Point2D {
x: number;
y: number;
}
let p3d = (point: Point3D) = >{}let p2d = (point: Point2D) = > { }
p3d = p2d / / compatible
p2d = p3d / / not compatible
Copy the code
3. Return value type
The return type of the target function must be the same as the return type of the source function, or a subtype
let f = (a)= > ({ name: 'Alice' })
let g = (a)= > ({ name: 'A', location: 'beijing' })
f = g / / compatible
g = f / / not compatible
Copy the code
4. Function overload
Function overload list (target function)
function overload(a: number, b: number) :number;
function overload(a: string, b: string) :string;
Copy the code
Concrete implementation of a function (source function)
function overload(a: any, b: any) :any {}Copy the code
The object function must have more parameters than the source function to be compatible
function overload(a:any,b:any,c:any) :any {} // There are more arguments than the first function in the overload list, i.e. the source function has more arguments than the target function
Copy the code
Incompatible return value types
function overload(a:any,b:any) {} // Remove any from the return value, incompatible
Copy the code
Enumeration type compatibility
enum Fruit { Apple, Banana }
enum Color { Red, Yello }
Copy the code
Enumeration types and numeric types are fully compatible
let fruit: Fruit.Apple = 4
let no: number = Fruit.Apple
Copy the code
Enumeration types are completely incompatible with each other
let color: Color.Red = Fruit.Apple / / not compatible
Copy the code
Class compatibility
If two classes have the same instance member, then their instances are compatible with each other
class A {
constructor(p: number, q: number) { }
id: number = 1
}
class B {
static s = 1
constructor(p: number) { }
id: number = 2
}
let aa = new A(1.2)
let bb = new B(1)
// Both instances are fully compatible; static members and constructors are not compared
aa = bb
bb = aa
Copy the code
Private property
There are two ways to have a private attribute in a class, if one class has a private attribute and the other doesn’t. Nothing is compatible with something, and if both classes are compatible with each other, then both classes are incompatible.
If one class has private attributes and another class inherits from that class, the two classes are compatible.
class A {
constructor(p: number, q: number) { }
id: number = 1
private name:string = ' ' // Add A private attribute to class A, aa is not compatible with bb, bb is compatible with AA, if A private attribute is added to class B, then both classes are incompatible
}
class B {
static s = 1
constructor(p: number) { }
id: number = 2
}
let aa = new A(1.2)
let bb = new B(1)
aa = bb / / not compatible
bb = aa / / compatible
// if A has private attributes, aa and cc are compatible with each other
class C extends A { }
let cc = new C(1.2)
// Instances of both classes are compatible
aa = cc
cc = aa
Copy the code
Generic compatible
A generic interface
When the generic interface is empty, generics specify different types and are compatible.
interface Empty<T> {}
let obj1:Empty<number> = {}
let obj2:Empty<string> = {}
/ / compatible
obj1 = obj2
obj2 = obj1
Copy the code
If a generic interface has an interface member, the types are incompatible
interface Empty<T> {
value: T
}
let obj1:Empty<number> = {}
let obj2:Empty<string> = {}
// Error, both incompatible
obj1 = obj2
obj2 = obj1
Copy the code
Generic function
Two generic functions are compatible if they have the same definition and do not specify type parameters
let log1 = <T>(x: T): T= > {
return x
}
let log2 = <U>(y: U): U= > {
return y
}
log1 = log2
log2 = log1
Copy the code
Compatibility Summary
- Compatibility between structures: those with fewer members are compatible with more members
- Compatibility between functions: those with many parameters are compatible with those with few parameters
Type protection mechanism
This refers to TypeScript’s ability to ensure that variables are of a specific type in specific blocks (type-protected blocks). Properties of this type can be carefully referenced in this block, or methods of this type can be called.
Pre-code, on which subsequent code runs
enum Type { Strong, Week }
class Java {
helloJava() {
console.log('hello Java')
}
java: any
}
class JavaScript {
helloJavaScript() {
console.log('hello JavaScript')
}
javaScript: any
}
Copy the code
Implementing the getLanguage method directly using lang.helloJava to determine whether it exists or not is an error
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new JavaScript()
// If you want to check whether lang.helloJava exists based on the type of the lang instance, you will get an error, because lang is now a combination of Java and JavaScript
if (lang.helloJava) {
lang.helloJava()
} else {
lang.helloJavaScript()
}
return lang
}
Copy the code
You can use type assertions to solve this problem
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new JavaScript()
Type assertion is needed to tell TS what type the current lang instance is
if ((lang as Java).helloJava) {
(lang as Java).helloJava()
} else {
(lang as JavaScript).helloJavaScript()
}
return lang
}
Copy the code
The first method of type protection is instanceof
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new JavaScript()
// Instanceof can determine which class the instance belongs to, so that TS can determine.
if (lang instanceof Java) {
lang.helloJava()
} else {
lang.helloJavaScript()
}
return lang
}
Copy the code
Type protection The second method, in, determines whether an attribute belongs to an object
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new JavaScript()
// In can determine if a property belongs to an object, as shown in helloJava and Java
if ('java' in lang) {
lang.helloJava()
} else {
lang.helloJavaScript()
}
return lang
}
Copy the code
Type protection A third method, Typeof type protection, can help us determine basic types
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new JavaScript()
// x is also a union type, typeof type protection, can determine the basic type.
if (typeof x === 'string') {
x.length
} else {
x.toFixed(2)}return lang
}
Copy the code
Type protection The fourth method is to determine the type of an object by creating a type protection function
The return value of a type protection function is a little different, using is, called a type predicate
function isJava(lang: Java | JavaScript) :lang is Java {
return (lang asJava).helloJava ! = =undefined
}
Copy the code
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new JavaScript()
// Determine the type of an object by creating a type protection function
if (isJava(lang)) {
lang.helloJava()
} else {
lang.helloJavaScript()
}
return lang
}
Copy the code
conclusion
Different judgment methods have different application scenarios:
- Typeof: determines the typeof a variable (mostly for basic types)
- Instanceof: Determines whether an instance belongs to a class
- In: Determines whether an attribute belongs to an object
- Type-protected functions: Some of the decisions may be more than a single statement can handle, requiring more complex logic and suitable encapsulation in a function
High-level types
Cross type
Use ampersand. Although called a cross type, it is the union of all types taken.
interface DogInterface {
run(): void
}
interface CatInterface {
jump(): void
}
// Interleaved types use ampersand. Although called a cross type, it is the union of all types taken.
let pet: DogInterface & CatInterface = {
run() { },
jump() { }
}
Copy the code
The joint type
The declared type is indeterminate and can be one of several types. In addition to the type specified in TS, there are also string literal union types and numeric literal union types
let a: number | string = 1;
// String literal associative type
let b: 'a' | 'b' | 'c' = 'a'
// Numeric literal union type
let c: 1 | 2 | 3 = 1
Copy the code
Object association type
The union type of an object can only take attributes that are common to both types, so the union type of an object can only access the intersection of all types
// Connect DogInterface and CatInterface
class Dog implements DogInterface {
run() { }
eat() { }
}
class Cat implements CatInterface {
jump() { }
eat() { }
}
enum Master { Boy, Girl }
function getPet(master: Master) {
// pet is the joint type of Dog and Cat. Only the joint type can access the intersection of all types
let pet = master === Master.Boy ? new Dog() : new Cat()
pet.eat()
// pet.run() // Cannot access, an error is reported
return pet
}
Copy the code
Distinguishable union types
This pattern is a type protection method that combines union and literal types. If a type is a combination of multiple types, and there is a common property between each type, we can create different type protection blocks by virtue of this common property.
The core is to create different code-protected blocks using two or more types of common attributes
The following functions are fine if they only have Square and Rectangle combined, but once the extension adds Circle, the type check will not work properly and will not return an error, which is what we expect in the code.
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: 'rectangle';
width: number;
height: number;
}
interface Circle {
kind: 'circle';
r: number;
}
type Shape = Square | Rectangle | Circle
// The following Rectangle function is fine if it has only Square and Rectangle, but if the extension adds Circle, it will not run properly and will not return an error.
function area(s: Shape) {
switch (s.kind) {
case "square":
return s.size * s.size;
break;
case "rectangle":
return s.width * s.height;
break; }}console.log(area({ kind: 'circle', r: 1 }))
// undefined, error not reported, this time we want code to have an error notification
Copy the code
If you want to get the correct error notification, the first method is to set an explicit return value, and the second method is to use the never type.
The first method is to set an explicit return value
// The function is missing the end of the return statement, return type does not include "undefined"
function area(s: Shape) :number {
switch (s.kind) {
case "square":
return s.size * s.size;
break;
case "rectangle":
return s.width * s.height;
break; }}Copy the code
The second method is to use the never type. The principle is to write a function at the last judgment branch of default, set the parameter to never, and then pass in the parameters of the outermost function. Normally, the default branch will not be executed.
function area(s: Shape) {
switch (s.kind) {
case "square":
return s.size * s.size;
break;
case "rectangle":
return s.width * s.height;
break;
case "circle":
return Math.PI * s.r ** 2;
break;
default:
return ((e: never) = > {throw new Error(e)}) (s) // This function is used to checksWhether it isneverType, ifsisneverType, indicating that all previous branches are covered, ifsnotneverType, indicating that the previous branch is missing and needs to be filled in. }}Copy the code
The index type
Query operator of index type
Keyof T represents the combined type of all public property literals of type T
interface Obj {
a: number;
b: string;
}
let key: keyof Obj
/ / the type of key is Obj properties of the combination of a and b type: let the key: "a" | "b"
Copy the code
Index access operator
T[K] represents the type represented by the attribute K of object T
interface Obj {
a: number;
b: string;
}
let value: Obj['a']
// The type of value is the type of Obj's property A: let value: number
Copy the code
Generic constraint
T extends U A generic variable can inherit from a type to obtain certain properties
Take a look at the problem with the following code snippet.
let obj = {
a: 1,
b: 2,
c: 3
}
// The following function does not fail to access attributes that do not exist in obj.
function getValues(obj: any, keys: string[]) {
return keys.map(key= > obj[key])
}
console.log(getValues(obj, ['a'.'b']))
console.log(getValues(obj, ['e'.'f']))
// undefined, undefined is displayed, but the TS compiler does not report an error.
Copy the code
To solve the following
function getValuest<T.K extends keyof T> (obj: T, keys: K[]) :T[K] []{
return keys.map(key= > obj[key])
}
console.log(getValuest(obj, ['a'.'b']))
/ / the console. The log (getValuest (obj, [' e ', 'f'])) / / that would be an error
Copy the code
Mapping type
You can generate a new type from an old type
The following code uses the TS built-in mapping types
interface Obj {
a: string;
b: number;
c: boolean
}
// The following three types, called homomorphisms, only apply to properties of Obj and do not introduce new properties
// Make all attributes of an interface read-only
type ReadonlyObj = Readonly<Obj>
// Make all attributes of an interface optional
type PartialObj = Partial<Obj>
// Can extract a subset of interfaces
type PickObj = Pick<Obj, 'a' | 'b'>
// Non-homomorphisms create new attributes
type RecordObj = Record<'x' | 'y', Obj>
// Create a new type and introduce the specified new type as
/ / {
// x: Obj;
// y: Obj;
// }
Copy the code
Conditions in the
T extends U ? X : Y
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object"
type T1 = TypeName<string> Type T1 = "string"
type T2 = TypeName<string[] >Type T2 = "object"
Copy the code
Distributed condition type
(A | B) extends U ? X: Y is equivalent to (A extends U? X : Y) | (B extends U ? X : Y)
/ / connected to
type T3 = TypeName<string | string[] >/ / the type: type T3 = "string" | "object"
Copy the code
Usage 1: Diff operations can be implemented using distributed condition types
type T4 = Diff<"a" | "b" | "c"."a" | "e"> / / that is, the type of T4 = "b" | "c"
// Break down the steps
// Diff<"a","a" | "e"> | Diff<"b","a" | "e"> | Diff<"c", "a" | "e">
/ / distribution results are as follows: never | | "b" "c"
/ / in the end get the literal combination type "b" | "c"
Copy the code
Filter out null and undefined values on the basis of Diff.
type NotNull<T> = Diff<T, undefined | null>
type T5 = NotNull<string | number | undefined | null> / / that is, the type T5 = string | number
Copy the code
All of the above type aliases have built-in types in the TS class library
Diff => Exclude<T, U>
NotNull => NonNullable<T>
In addition, there are many built-in types, such as Extract
from type T that can be assigned to U
type T6 = Extract<"a" | "b" | "c"."a" | "e"> Type T6 = "a"
Copy the code
For example: the return value type used to extract function types ReturnType
Start with an implementation of ReturnType
. Infer represents the type variable to be inferred from the extends condition statement.
type ReturnType<T extends(... args:any) = >any> = T extends(... args:any) => infer R ? R : any;
Copy the code
If T can be assigned to a function that can take any argument to be inferred as R, and if it can’t, return any.
type T7 = ReturnType<(a)= > string> // that is, type T7 = string
Copy the code