This is the 13th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

preface

Hello, everyone. Continue learning about TypeScript today. In a previous article we looked at the basic data types in TypeScript, but there is another data type in TypeScript called advanced types. Let’s take a look at TypeScript’s advanced types, what they look like, and how they can be used.

High-level types

  • Cross type

A cross type is simply a combination of types:

  • To combine multiple types into a single type, the merged type contains all the members and features of all the pre-merged types.
  • Cross types are generally used to merge reference data types such as interfaces or classes, not the base types we studied earlier
  • Cross types are used to concatenate multiple types with the **&** symbol

Let’s take a look at the code example. Suppose we have a Person class with attributes like name and age and an IPerson interface with methods like talk and run. Let’s implement a cross type using these two types

class Person{
	name:string
	age:number
	sex:string
}

interface IPerson{
	say(world:string) :string;
	run(km:number) :number;
}

// Per is a crossover type that contains Person and IPerson
let per: Person & IPerson;
When assigning a value to per, per must include all members of the Person class and the IPerson interface
per = {
	name:'Yannis'.age:28.sex:'nv',
	say(world:string) :string{
		return world
	},
	run(km:number) :number{
		returnkm; }}// Cross types do not apply to underlying data types. For example, a string and number cross type is allowed, but the merged type is never and cannot be assigned, as follows
let obj : string & number;
obj = 'hello'
obj = 1
// Both assignments are wrong because no value is both string and number
Copy the code
  • The joint type

A cross type is a merging of multiple types into a single type that contains all of the types’ members and attributes. Union types are similar to cross types and are related to multiple types, except that union types are applicable to all types (including the base types we learned earlier).

  • A union type takes one of multiple types, that is, it matches only one of multiple types (the types are “or”)
  • Union types apply to all types, including base types
  • Joint type is used between multiple types “|” space, such as let obj: string | number;
  • If each type in the union type is the base type, then only one of them is assigned
  • If each type in the union type is a non-base type (such as a class or interface), then we can access only the common members of all types of the union type when we use members of the union attribute.

Let’s say we have a requirement to define a function that takes a single argument of either type string or number, in which case we can use union types

// Each type in the union type is a base type
function getValue(value:string | number) :string|number{
	return value;
}

getValue(3); / / 3
getValue("Hello");// 'Hello'
getValue(true);// Error reported, type mismatch

// Each type in the union type is a non-base type
interface IBird{
	fly();
	layEggs:number;
}

interface IFish{
	swim();
	layEggs:number;
}
let animal: IBird | IFish;
animal.layEggs;// No error because layEggs is a common property of IBird and IFish
animal.swim();/ / error: the Property 'swim' does not exists on type IBird | IFish, Property 'swim' does not exists on the type of IBird
animal.fly();/ / error: the Property 'fly' does not exists on type IBird | IFish, Property 'fly' does not exists on type IFish
Copy the code

In the code above, we can see that both swim and fly access will return an error. For example, we can use type assertions to access members that are not in common. Look at the following code:

interface IBird{
	fly();
	layEggs:number;
}

interface IFish{
	swim();
	layEggs:number;
}
let animal: IBird | IFish;
animal.layEggs;// There is no problem with the co-membership
// Use type assertions to access non-shared members
if((<IBird>animal).fly){// Deduce that animal is IBird
	(<IBird>animal).fly()
}else{
	(<IFish>animal).swim()
}
Copy the code

This does what we want, but it’s obviously a little inconvenient to use type assertions every time. What other implementations are there? The answer is yes, in order to access both Swim and fly, TypeScript provides a type protection mechanism that allows us to access both public and non-public members of federated types. Let’s take a look at how TypeScript type protection is implemented.

Type protection mechanism

  • Custom type protection

Type protection in TypeScript is expressions that are checked at run time to ensure that a type is in a scope. To implement a type protection, we simply define a function and let the return value of that function be a “type predicate.” Here’s a new concept: “type predicates.” What are type predicates? The so-called type predicate is the form of the parameter is type, for example: animal is Fish. Let’s use type protection again to implement the above requirements.

// To use type protection, you first need to define a function whose return type is a type predicate
function isFish(animal: IFish | IBird) :animal is IFish{// We assume animal is IFish
	return(<IFish>animal).swim ! = =undefined;
}
if(isFish(animal)){
	animal.swim();
}else{
	animal.fly();
}
Copy the code

In the example code above, Animal is IFish is the type predicate. Whenever isFish is called with some variable, TypeScript reduces the variable to that specific type (IFish in this case). In the isFish method above, typescript not only knows that the animal variable in the if branch is of type IFish, it also knows that in the else branch animal must not be of type IFish, but must be of type IBird.

  • Typeof type protection

In addition to using custom type protection, we can also use TypeScript’s built-in Typeof type protection. We know that Typeof is used in javascript to detect data types, so in TypeScript we can also use Typeof to detect union types. Typeof, however, can only detect “number”, “string”, “Boolean”, and “symbol”, not reference types. Going back to the getValue example above, we added a slightly different requirement: we need to do different logic processing in the function depending on the typeof the argument passed in, and we can use typeof type protection to handle this.

function getValue(value:string | number) :void{
	if(typeof value === 'string') {// do somthing for string
	}else{
		// do somthing for number}}Copy the code
  • Instanceof protection

We mentioned above that typescript provides Typeof protection, which allows us to detect types without defining functions. However, Typeof protection is limited in that it can only detect a limited number of basic types. What about application types? Don’t worry, typescript also provides us with another type protection mechanism – instanceof type protection. Like instanceof in javascript, instanceof can be used in typescript to check whether a variable is an instanceof a type and then access all members of that type.

interface IBird{
	fly();
	layEggs:number;
}

interface IFish{
	swim();
	layEggs:number;
}
let animal: IBird | IFish;
animal.layEggs;// There is no problem with the co-membership
// Use type assertions to access non-shared members
if(animal instanceof IBird){// Deduce that animal is IBird
	animal.fly()
}else{
	animal.swim()
}
Copy the code

Type the alias

Typescript also provides us with a new syntax – type aliasing. A type alias simply gives a type a new name to make it easier to use.

  • Use the type keyword to define a type alias
  • Type aliases can be used for any type, such as base type, reference type, cross type, union type, and so on
  • An alias for a class does not create a new type; it just creates a name to refer to that type.
  • Type aliases also support generics

Also: While type aliases can be used for basic types, it doesn’t seem to make sense to have an alias for the base type. Type aliases are often used for complex types, such as the cross and union types we learned above.

type Name = string; // It has no practical meaning
type StringOrNumer = string | number;
type funType = () = > string;

type Container<T> = {value:T};
type Tree<T> = {
	value:T
	left:Tree<T>
	right:Tree<T>
}
Copy the code

conclusion

In this article we learned something new: advanced types. There are not only basic types in typescript, but also so many advanced types that are most commonly used in class library encapsulation. To recap, in this article we learned about cross types, union types, protection mechanisms for types, and aliases for types. Interested partners can continue to in-depth research, this share here.

Like small partners welcome to praise the message plus attention oh!