preface
Ts entry for a period of time, but some advanced types are always confused, but also need to see the document, so summed up some work often used in some advanced types. So you need to know a little bit about TS basics before reading.
First, cross type
A crossing type merges multiple types into a new type, which has the properties of all participating types and is essentially a merging operation. The form is as follows:
T & U // Example:function extend<T , U>(first: T, second: U) : T & U {
let result: <T & U> = {}
for (let key in first) {
result[key] = first[key]
}
for (let key in second) {
if(! result.hasOwnProperty(key)) { result[key] = second[key] } }return result
}
Copy the code
Second, the type of association
Joint type with | up each type, said can only take one of these types, such as: number | string | Boolean type only is one of the three, cannot coexist. If a value is of a union type, then we can only access properties or methods that are in common, essentially an intersection, such as:
interface Cat {
eat(food) : void
miao() : string
}
interface Dog {
eat(food) : void
wang() : string
}
function getPet() : Cat | Dog {
...
}
letPet = getPet() pet.eat() // Correct PetCopy the code
Type protection and type differentiation
Union types allow a value to be of different types, but with that comes the problem of an error when accessing non-common methods. So how do you distinguish the specific types of values, and how do you access common members?
1. Use type assertions
let pet = getPet()
if((<Cat>pet).miao) {
(<Cat>pet).miao()
} else {
(<Dog>pet).wang()
}
Copy the code
Since type assertions require a bunch of crappy Angle brackets, isn’t there a cleaner way to determine a type?
2. Use type protection
function isCat(pet: Cat | Dog) : pet is Cat {
return(<Cat>pet).miao ! == undefined }Copy the code
This form of param is SomeType is type protection. We can use it to specify the specific form of a variable of the joint type. When called, TS will reduce the variable to that specific type, and the call is legal
if(isCat(pet)) {pet.miao() // Correct}else {
pet.wang()
}
Copy the code
This is possible because typescript knows through type protection that pet in an if statement must be of type Fish, and else statements must be of type Bird if pet is not of type Fish
3. The type and instanceof
When typeof and Instanceof are used, typescript automatically limits the type to a specific type so that we can safely use type-specific methods and properties within the statement body, such as:
function show(param: number | string) {
if (typeof param === 'number') {
console.log(`${param} is number`)
} else {
console.log(`${param} is string`)
}
}
Copy the code
But Typeof only supports number, String, Boolean, or symbol (only those cases can be considered type protected)
If it’s a class, we can use Instanceof
let an = getSomeKindType()
if(an instanceof Animal) {an //if(an instanceof People) {anCopy the code
Ts requires that the right side of instanceof be a constructor, which TS refines to:
- The type of the prototype property of this constructor
- The union of types returned by the constructor
4. The type can be null
Null and undefined can be assigned to any type because they are a valid value for all other types, such as:
// All the following statements are validlet x1: number = null
let x2: string = null
let x3: boolean = null
let x4: undefined = null
let y1: number = undefined
let y2: string = undefined
let y3: boolean = undefined
let y4: null = undefined
Copy the code
In typescript, we can use the –strictNullChecks flag. When we declare a variable, we do not automatically include null or undefined, but we can manually use union types to explicitly include variables, such as:
letX = 123 x = null // ErrorletY: number | null = 123, y = null / / allow y = undefined / / an error, ` undefined ` cannot be assigned to ` number | null `Copy the code
When open – strictNullChecks, optional parameter/attributes will be automatically add | undefined, such as:
functionfoo(x: number, y? : number) {return(x + y | | 0)} foo (1, 2) / / allow the foo (1) / / allow the foo (1, undefined) / / allow foo (1, null) / / an error, do not allow null assigned to ` number | undefined ` typeCopy the code
Type aliases
A type alias can give a new name to an existing type. It is similar to but different from an interface, because a type alias can work with primitive values, union types, tuples, and any other type that requires handwriting. The syntax is as follows:
type Name = string
An alias does not create a new type, it just creates a new name to reference an existing type. So when you hover your mouse over an alias in VSCode, the referenced type is displayed
1. Generic alias
type Container<T> = {
value: T
}
let name: Container<string> = {
value: 'hello'
}
Copy the code
2. Differences between interface and interface
- Aliases cannot be extends or implements
- An alias is not used for error messages, but is displayed directly as the referenced type when the mouse hovers
3. String literal types
The string literal type allows us to define an alias. Variables of type alias can only take a fixed number of values, such as:
type Easing = 'ease-in' | 'ease-out' | 'ease-in-out'
let x1: Easing = 'uneasy'// Error: Type'"uneasy"' is not assignable to type 'Easing'
let x2: Easing = 'ease-in'/ / allowCopy the code
4. Identifiable associations
You can combine string literal types, union types, type protection, and type aliases to create a high-level pattern that recognizes unions (also known as label unions or algebraic data types) with three elements:
- Has ordinary string literal attributes — recognizable characteristics
- A type alias used to contain unions of those types — unions
- Type protection on this property
To create a recognisable union type, we first declare the interfaces to be federated. Each interface must have an identifiable characteristic, such as the kind attribute:
interface Square {
kind: 'square'
size: number
}
interface Rectangle {
kind: 'rectangle'
width: number
height: number
}
interface Circle {
kind: 'circle'
radius: number
}
Copy the code
Currently, the interfaces are not related, so we need to use type aliases to combine them, such as:
type Shape = Square | Rectangle | Circle
Copy the code
Now, use identifiable unions such as:
function area(s: Shape) {
switch (s.kind) {
case 'square':
return s.size * s.size
case 'rectangle':
return s.height * s.width
case 'circle':
return Math.PI * s.radius ** 2
}
}
Copy the code
Polymorphic this type
The polymorphic this type represents a subtype containing a class or interface, for example:
class BasicCalculator {
public constructor(protected value: number = 0) {
}
public currentValue(): number {
return this.value
}
public add(operand: number): this {
this.value += operand
return this
}
public multiply(operand: number): this {
this.value *= operand
return this
}
}
let v = new BasicCalculator(2).multiply(5).add(1).currentValue() // 11
Copy the code
Because of the use of this, when subclasses inherit from their parent, the new class can use the previous method without making any changes, such as:
class ScientificCalculator extends BasicCalculator {
public cconstructor(value = 0) {
super(value)
}
public sin() {
this.value = Math.sin(this.value)
return this
}
}
let v = new BasicCalculator(2).multiply(5).sin().add(1).currentValue()
Copy the code
Without this type, the ScientificCalculator would not be able to inherit from The BasicCalculator while maintaining the continuity of the interface. The multiply method returns the BasicCalculator type, which has no sin method. However, with this type, multiply returns this, in this case ScientificCalculator
Index type
Index types enable the compiler to examine code that uses dynamic attribute names. For example, if we want to write a function that picks the values of some elements in an object, then:
function pluck<T, K entends keyof T>(o: T, names: K[]) : T[K][] {
return names.map(n => o[n])
}
interface Person {
name: string
age: number
}
let p: Person = {
name: 'LL',
age: 18
}
let res = pluck(p, ['name') / / permitCopy the code
The above code is explained as follows:
- First, use the keyof keyword, which is the index type query operator that can get a union of known public attribute names on any type T. Such as example, keyof T equivalent to ‘name’ | ‘age’
- Then, K extends keyof T show that K value of limited to ‘name’ | ‘age’
- T[K] represents the type of the corresponding key element in the object, so in this example, the name attribute in p is of type string, so T[K] is equivalent to Person[name], which is equivalent to type string, so the return is string[], So res is of type string[]
Therefore, according to the above examples, there are three inferences:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
let obj = {
name: 'LL',
age: 18,
male: true
}
let x1 = getProperty(obj, 'name'// Yes, x1 is of type stringlet x2 = getProperty(obj, 'age'X2 is of type numberlet x3 = getProperty(obj, 'male'X3 is of type Booleanlet x4 = getProperty(obj, 'hobby') // Error: Argument oftype '"hobby"' is not assignable to parameter of type '"name" | "age" | "male"'.
Copy the code
Index type and string index signature
Keyof and T[K] interact with string index signatures. If there is a type with a string index signature, then keyof T is string and T[string] is the type of the index signature, as in:
interface Demo<T> {
[key: string]: T
}
letKeys: keyof Demo< Boolean > // The type of keys is stringlet value: Demo<number>['foo'] // Value is of type numberCopy the code
7. Mapping type
We may encounter requirements such as: 1) Make every attribute of an existing type optional, such as:
interface Person {
name: string
age: number
}
Copy the code
Available versions are:
interface PersonPartial { name? : string age? : number }Copy the code
2) Or make each property read-only, as in:
interface PersonReadonly {
readonly name: string
readonly age: number
}
Copy the code
Typescript now provides mapping types that make conversion easier, where the new type converts every property of the old type in the same way.
type Readonly<T> = {
readonly [P inKeyof T: T[P]}//ts source codetype Partial<T> = {
[P inkeyof T]? : T[P]}//type PersonReadonly = Readonly<Person>
type PersonPartial = Partial<Person>
Copy the code
Ts also provides the Required, Pick, and Record types. The following is the source code for these types:
type Required<T> = {
[P inkeyof T]-? : T[P]; }type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
type Record<K extends keyof any, T> = {
[P in K]: T;
}
type personRequired = Required<Person>;
// personRequired === {name: string; age: number}
type personPick = Pick<Person, "name">;
// person5 === {name: string}
type personRecord = Record<'name' | 'age', string>
// personRecord === {name: string; age: string}
Copy the code
We can also write more generic mapping types, such as:
// Can be a null typetype Nullable<T> {
[P inKeyof T], T [P] | null} / / packing a type attributetype Proxy<T> = {
get(): T
set(value: T): void
}
type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>
}
function proxify(o: T): Proxify<T> {
// ...
}
let proxyProps = proxify(props)
Copy the code
Extrapolating from the mapping type (unpacking)
The opposite of wrapping a type, as shown above, is unpacking. For example:
function unproxify<T>(t: Proxify<T>): T {
let result = <T>{}
for (const k in t) {
result[k] = t[k].get()
}
return result
}
let originalProps = unproxify(proxyProps)
Copy the code