“This is the first day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021”
TypeScript has been around for years, and I’m sure you’re starting to use it in your daily work. However, I’ve found that many TS projects simply change the JS file suffix to TS, which doesn’t take advantage of TypeScript’s powerful type system. There are a lot of any and assertions in your project, so how different is that from writing JS? After all, TypeScript type declarations are eliminated when they are compiled into JS.
Why the emphasis on TypeScript’s type system?
This is a good question and a good start to getting serious about using TypeScript types.
Types have a proven ability to improve code quality and understandability.
- Types can improve agility when refactoring. It’s better for the compiler to catch errors than to have things fail at run time;
- Types are one of the best forms of documentation you can have. The function signature is a theorem and the function body is a proof
For example, in daily development, we always need to connect to the API provided by the back end and bind the returned data to the view layer, which is the modern front-end mode anyway. However, some time after the project was launched, the API field of the back end was modified. If there is no type definition, we need to manually read the previous logic and debug. If there are many components involved, the change may be missed. In this case, the type system can help us do express validation, modifying the data structure at the source of the structure returned by the API, and throwing errors at compile time.
How to write a type definition?
Let’s start with a simple example and define some simple animal classes:
interface Animal {
color: string;
age: number;
sex: string;
}
interface Cat extends Animal {
sleep: () = > void;
}
interface Dog extends Animal {
eat: () = > void;
}
Copy the code
Then we expect them to do something, but Cat can only sleep, Dog can only eat, so the code looks like this:
function catAction(animal: Cat, sleep: () => void) :Cat {
Object.assign(animal, {
sleep,
})
return animal
}
function dogAction(animal: Dog, eat: () => void) :Dog {
Object.assign(animal, {
sleep,
})
return animal
}
Copy the code
If you notice any duplication, since they all inherit from the same class, then we can implement it again using generics:
function Action<T extends Animal> (animal: T, opts: Object) :T {
Object.assign(animal, opts)
return animal
}
Copy the code
In the very simple example above, with the type variable T, we not only establish a dynamic relationship between the input parameter and the type of the return value, but also establish a constraint between the input parameter, which is a powerful expression. And since such variables almost always act as placeholders semantically, we tend to abbreviate them to something like T/U/V.
We can pass in some data that does not conform to the type definition.
function Action<T extends Animal> (animal: T, opts: Record<string, () = >void>) :T {
Object.assign(animal, opts)
return animal
}
Copy the code
Use the Record helper class to enforce constraints on k-V Types. TypeScript also has many of these helper Types built in, see Utility Types.
However, this is not enough, so it is necessary to move up to the level of type gymnastics, like this:
function Action<T extends Animal.U extends { [K in keyof T]: T[K] }>(animal: T, opts: U): T {
Object.assign(animal, opts)
return animal
}
// If you feel that not all parameters need to be given each time, but only some, then we can use the helper class again:
function Action<T extends Animal.U extends Partial<{ [K in keyof T]: T[K] }>>(animal: T, opts: U): T {
Object.assign(animal, opts)
return animal
}
const cat: Cat = {
color: 'white'.age: 3.sex: 'girl'.sleep: () = > true,
}
Action(cat, {
sleep: () = > true,})Copy the code
This way, with a few lines of type-space code, you can leverage the power of TypeScript type checkers to optimize validation logic that needs to be done at runtime directly to compile time.
summary
Taking advantage of TypeScript’s type system not only improves development efficiency but also improves overall development quality.
Of course, in some projects with relatively simple business logic, it is not recommended to use TypeScript’s type system extensively for several reasons:
- The logic is simple. TypeScript’s type system is designed to enforce logic rigor.
- Type definitions are extra work, and it is the cost of future stability and refactoring in exchange for the time now. Is it worth it if the future change time is as short as a variable substitution?