“This is my 27th day of participating in the First Challenge 2022. For more details: First Challenge 2022.”
The knowledge graph in this paper is divided into three parts: basic, advanced and practical, as follows:
The knowledge listed in these three knowledge maps seems to be a lot. In fact, except for the advanced part of generics, advanced type (ii) and practical part, the rest is very easy to understand.
The knowledge points listed in this paper are more concise than the official documents, but they can basically cover the DAILY development needs of TS knowledge, and there is no problem to deal with development after learning.
The introduction
I was exposed to TS when I just graduated in 2011, because the project in the group used TS, so I had to learn it.
But IN fact, I only understand some very fragmentary concepts of TS, because the daily work is a process of turning screws, just need to copy and paste other people’s code to run, I don’t need to know why a certain type of writing.
Now I have been working for nearly 3 years, but I still only know how to use it, although it does not affect my normal work.
But in 2022, not to mention React, Vue has recently switched to the default version of Vue3, embracing TS. TS will soon become a basic skill in the front end, and it will be too late to learn it.
I was obsessed with TS documents before, which was very boring and difficult to get started. I was persuaded to quit for many times, and my learning efficiency was very low, and I forgot it after learning.
Later I found that we do not necessarily need to learn as much knowledge as official documents, as long as we can get started, TS documents are more suitable for beginners to refer to and use, but not for beginners.
Many people who learn TS deeply, even do TS type gymnastics, brush TS like LeetCode, as a brick moving person, there is really no need to follow the trend, after all, it is not necessary to learn so deep to deal with the daily increase, deletion, change and check.
My philosophy has always been to use it to learn, don’t worry, people’s energy is limited. For example, when I need to understand Vue3 source code or design a library, the current TS knowledge reserve is not enough, and then do type gymnastics, it is not too late.
I recently re-learned TS, consulted many more easy-to-understand tutorials than official documents, summarized the key points in learning, and formed this article.
Our goal is that after learning this article, we can cope with daily development and know how to look up new knowledge when we encounter it.
Why learn TS
This is the trend. By 2022, TS is becoming a basic skill on the front end.
Just like a few years ago, at the end of the jquery era, when you couldn’t vue or react, you couldn’t get a job.
Even Vue is now Vue3 by default. Vue3 is bundled with Typescript, and in two years new projects will be TS, except for the old ones.
How to Learn this article
Don’t worry about the setting. Go to TS Playground and write all the examples in this article. Be sure to write them by hand.
When learning this article, you don’t have to read it from beginning to end, because it’s easy to get tired of reading too many concepts. You can keep it in your collection and read one or two concepts every day when fishing. You can finish reading them in a week or two.
Alin also learned TS every day while waiting for back-end integration and test acceptance, and learned one or two concepts a day. It took him just two weeks to finish this article.
You can regard this article as the most popular basic TS introduction article, read the official documents do not understand when the comparison to learn this article, perhaps can understand the official documents in some difficult concepts, all the knowledge of this article is only suitable for beginners.
In addition, don’t worry about the future will not configure the environment, engineering problems must have a lot of scaffolding support, such as (VUe-CLI, vite), learn the grammar is the essence.
Do it now!
TS and JS
TypeScript is a free and open source programming language developed by Microsoft. It is a superset of JavaScript, and essentially adds optional static typing and class-based object-oriented programming to the language. — Official documents
Speaking human is TS expands some functions of JS and solves some shortcomings of JS, which can be summarized in the following table.
TypeScript | JavaScript |
---|---|
A superset of JavaScript designed to address the code complexity of large projects | A scripting language for creating dynamic web pages. |
Strongly typed, supporting both static and dynamic typing | Dynamically weakly typed languages |
Errors can be found and corrected at compile time | Errors can only be found at run time |
Changing the data type of a variable is not allowed | Variables can be assigned different types of values |
See my article on strongly typed, weakly typed, statically typed, and dynamically typed languages.
Use a graph to describe the relationship between TS and JS,
JS has, TS has, JS does not, TS also has, after all, TS is a superset of JS.
Disadvantages of TS:
- Cannot be understood by the browser and needs to be compiled into JS
- There are learning costs, we used to write JS to get started need to spend time to understand, and TS some concepts are still a little difficult, such as generics.
TS based
This part of the content is relatively simple, students with JS foundation to write the example again to understand.
The base type
Boolean, number, and string
boolean
let isHandsome: boolean = true
Copy the code
Assignments that do not conform to the definition will report errors, which is an advantage of statically typed languages, which can help us detect errors in code ahead of time. number
let age: number = 18
Copy the code
string
let realName: string = 'lin'
let fullName: string = `A ${realName}` // Support template strings
Copy the code
Undefined and null
let u:undefined = undefined / / undefined type
let n:null = null / / null type
Copy the code
By default null and undefined are subtypes of all types. This means that you can assign null and undefined to variables of type number.
let age: number = null
let realName: string = undefined
Copy the code
However, if strictNullChecks is specified, null and undefined can only be assigned to void and their own, otherwise an error will be reported.
Any, unknown, and void
any
It’s not clear what type to use, but use any. These values may come from dynamic content, such as user input or third-party code libraries
let notSure: any = 4
notSure = "maybe a string" // Can be string
notSure = false // Can also be a Boolean type
notSure.name // You can call properties and methods as you like
notSure.getName()
Copy the code
It is not recommended to use any, otherwise you lose the meaning of TS.
Unknown type
Any is not recommended. What do I do when I don’t know what a type is?
You can use the unknown type
The unknown type represents any type. Its definition is similar to any, but it is a safe type, and it is illegal to do anything with unknown.
For example, if we have a function called divide,
function divide(param: any) {
return param / 2;
}
Copy the code
If param is set to any, TS will compile without exposing potential risks, such as passing an Object or function
Define param as type unknown,
function divide(param: unknown) {
return param / 2;
}
Copy the code
The TS compiler blocks potential risks, as shown below.
The/operator is used because the type of param is unknown.
This problem can be solved with type assertions,
function divide(param: unknown) {
return param as number / 2;
}
Copy the code
void
The void type is the opposite of any in that it means there is no type.
For example, a function that does not explicitly return a value returns Void by default
function welcome() :void {
console.log('hello')}Copy the code
Never type
The never type represents the types of values that never exist.
There are cases where the value never exists, for example,
- If a function throws an exception, the function never returns a value, because throwing an exception directly interrupts the program.
- Code in a function that executes an infinite loop such that the program never reaches the return value of the function.
/ / exception
function fn(msg: string) :never {
throw new Error(msg)
}
/ / death cycle
function fn() :never {
while (true) {}}Copy the code
The never type is a subtype of any type and can be assigned to any type.
No type is a subtype of never, and no type can be assigned to never (except never itself). Even any cannot be assigned to never.
let test1: never;
test1 = 'lin' Type 'string' is not assignable to Type 'never'
Copy the code
let test1: never;
let test2: any;
test1 = test2 Type 'any' is not assignable to Type 'never'
Copy the code
An array type
let list: number[] = [1.2.3]
list.push(4) // Call a method on an array
Copy the code
An error occurs when an item in an array is typed incorrectly
Push type pairs do not report errors
What if the array wants to put different data into each item? Use tuple types
A tuple type
The tuple type allows you to represent an array with a known number and type of elements that need not be of the same type.
let tuple: [number.string] = [18.'lin']
Copy the code
Write type error:
An error will be reported if you cross the boundary:
Methods that use arrays on tuples, such as push, do not get out of bounds
let tuple: [number.string] = [18.'lin']
tuple.push(100) // Only push numbers or strings
Copy the code
Push an undefined type, error reported
Function types
TS defines function types by defining input parameter types and output types.
The output type can also be ignored because TS can automatically infer the return value type from the return statement.
function add(x:number, y:number) :number {
return x + y
}
add(1.2)
Copy the code
The function does not explicitly return a value. By default, it returns Void
function welcome() :void {
console.log('hello');
}
Copy the code
Function expression writing
let add2 = (x: number.y: number) :number= > {
return x + y
}
Copy the code
Optional parameters
The argument is followed by a question mark to indicate that it is optional
function add(x:number, y:number, z? :number) :number {
return x + y
}
add(1.2.3)
add(1.2)
Copy the code
Note that optional arguments must be placed at the end of the function’s entry arguments, otherwise a compilation error will occur.
The default parameters
function add(x:number, y:number = 100) :number {
return x + y
}
add(100) / / 200
Copy the code
As with JS, we define the initial value in the input parameter.
Unlike optional arguments, default arguments may not be placed at the end of function entry arguments,
function add(x:number = 100, y:number) :number {
return x + y
}
add(100)
Copy the code
The add function passes only one argument. If you assume that x has a default value, you will get an error if you pass y.
The compiler decides that you only passed x, not Y.
If the parameter with a default value is not the last parameter, the user must explicitly pass undefined to get the default value.
add(undefined.100) / / 200
Copy the code
Function assignment
JS variable assignment is ok,
But in TS, you can’t just arbitrarily assign a function, you’ll get an error,
You can also define a function add3 by assigning add2 to add3
let add2 = (x: number.y: number) :number= > {
return x + y
}
const add3:(x: number, y: number) = > number = add2
Copy the code
A bit like the arrow function in ES6, but not the arrow function, TS encounters: the following code is used to write type.
Of course, it is also possible to assign the variable directly without defining the add3 type. TS will automatically infer the type during the variable assignment process, as shown in the following figure:
Function overloading
Function overloading refers to two functions with the same name but different parameter types or numbers. Its advantages are obvious. There is no need to split functions with similar functions into multiple functions with different function names.
Different parameter types
Let’s say we implement an add function that returns adding numbers if all the arguments are numbers, concatenating strings if all the arguments are strings,
function add(x: number[]) :number
function add(x: string[]) :string
function add(x: any[]) :any {
if (typeof x[0= = ='string') {
return x.join()}if (typeof x[0= = ='number') {
return x.reduce((pre, cur) = > pre + cur)
}
}
Copy the code
In TS, to implement function overloading, you need to declare the function multiple times, the first several times with the function definition, listing all the cases, and the last time with the function implementation, requiring a broader type, such as any in the example above.
Different number of parameters
So let’s say that the add function takes more arguments, so for example, we could pass in a parameter y, and if we pass y, we could add or concatenate y, and we could say,
function add(x: number[]) :number
function add(x: string[]) :string
function add(x: number[], y: number[]) :number
function add(x: string[], y: string[]) :string
function add(x: any[], y? :any[]) :any {
if (Array.isArray(y) && y.length > 0 && typeof y[0= = ='number') {
return x.reduce((pre, cur) => pre + cur) + y.reduce((pre, cur) => pre + cur)}if (Array.isArray(y) && y.length > 0 && typeof y[0= = ='string') {
return x.join() + ', '+'y.join()}if (typeof x[0= = ='string') {
return x.join()}if (typeof x[0= = ='number') {
return x.reduce((pre, cur) = > pre + cur)
}
}
console.log(add([1.2.3])) / / 6
console.log(add(['lin'.'18'])) // 'lin,18'
console.log(add([1.2.3], [1.2.3])) / / 12
console.log(add(['lin'.'18'], ['man'.'handsome'])) // 'lin,18,man,handsome'
Copy the code
It’s actually a little bit of a hassle to write, but it’ll be a little bit simpler to write when you get to generics, so you don’t have to worry too much about function overloading, but you know you have this concept, and you usually use generics to solve problems like this.
interface
The basic concept
Interface is designed by TS to define the type of object, which can describe the shape of the object.
To define an interface, use a capital letter.
interface Person {
name: string
age: number
}
const p1: Person = {
name: 'lin'.age: 18
}
Copy the code
The attribute must be exactly the same as when the type was defined.
Error: Attribute missing
Error: Attribute ()
Type prompt, significantly improve development efficiency:
Note: Interface is not a keyword in JS, so when TS is compiled into JS, these interfaces will not be converted, will be deleted, interface is only used for static checks in TS.
Optional attribute
It’s similar to the optional argument of a function, where you put a? This property is optional, such as the age property below
interface Person {
name: string age? : number }const p1: Person = {
name: 'lin',}Copy the code
Read-only property
If you want an attribute to remain unchanged, you can write:
interface Person {
readonly id: number
name: string
age: number
}
Copy the code
An error occurs when changing this read-only property.
Interface describes the function type
Interface can also be used to describe function types, as follows:
interface ISum {
(x:number.y:number) :number
}
const add:ISum = (num1, num2) = > {
return num1 + num2
}
Copy the code
Custom attributes (indexable types)
Above, the attribute must be exactly the same as when the type is defined. What if there are multiple indeterminate attributes on an object?
We could write it this way.
interface RandomKey {
[propName: string] :string
}
const obj: RandomKey = {
a: 'hello'.b: 'lin'.c: 'welcome',}Copy the code
If you define the property name as number, you have an array of classes that looks exactly like an array.
interface LikeArray {
[propName: number] :string
}
const arr: LikeArray = ['hello'.'lin']
arr[0] // Values can be accessed using subscripts
Copy the code
Of course, it’s not really an array, it doesn’t have methods on an array.
duck typing(The duck type)
So if you look at this, you can see that interface is written very flexibly, it’s not dogmatic.
Interfaces allow you to create a series of custom types.
In fact, interface has a catchy name: Duck typing.
When a bird is seen walking like a duck, swimming like a duck, and quacking like a duck, it can be called a duck. — James Whitcomb Riley
TS will compile as long as the data meets the type defined by the interface.
Here’s an example:
interface FunctionWithProps {
(x: number) :number
name: string
}
Copy the code
The FunctionWithProps interface describes a function type and adds the name attribute to that function type, which may seem completely different, but this definition works perfectly.
const fn: FunctionWithProps = (x) = > {
return x
}
fn.name = 'hello world'
Copy the code
In fact, React’s FunctionComponent does this,
interfaceFunctionComponent<P = {}> { (props: PropsWithChildren<P>, context? :any): ReactElement<any.any> | null; propTypes? : WeakValidationMap<P> |undefined; contextTypes? : ValidationMap<any> | undefined; defaultProps? : Partial<P> |undefined; displayName? :string | undefined;
}
Copy the code
Don’t worry about all the stuff you don’t understand. We’ll analyze it later in the field.
At the moment we only care about FunctionComponent as a function type described by interface and adding a bunch of properties to that function type, which is completely different but perfectly normal.
This is duck typing and interface, very flexible.
class
As we know, JS is based on prototype and prototype chain to achieve object-oriented programming, ES6 has a new syntax sugar class.
TS enhances JS classes with public, private, and protected modifiers.
In TS, we write the same as in JS, but define some types. Let’s review the encapsulation, inheritance, and polymorphism of classes through the following examples.
Basic writing
Define a Person class with the attribute name and method speak
class Person {
name: string
constructor(name: string) {
this.name = name
}
speak() {
console.log(`The ${this.name} is speaking`)}}const p1 = new Person('lin') // Create an instance
p1.name // Access properties and methods
p1.speak()
Copy the code
inheritance
Using the extends keyword for inheritance, define a Student class that inherits from the Person class.
class Student extends Person {
study() {
console.log(`The ${this.name} needs study`)}}const s1 = new Student('lin')
s1.study()
Copy the code
After inheritance, instances on the Student class can access properties and methods on the Person class.
The super keyword
Note that the Student class does not define its own attributes. You can omit super, but if the Student class has its own attributes, the super keyword is used to inherit the attributes from the parent class.
For example, if you add a grade attribute to the Student class, say:
class Student extends Person {
grade: number
constructor(name: string,grade:number) {
super(name)
this.grade = grade
}
}
const s1 = new Student('lin'.100)
Copy the code
Not writing super will cause an error.
polymorphism
A subclass overrides the methods of its parent class, and the subclass and parent class call the same method differently.
class Student extends Person {
speak() {
return `Student The ${super.speak()}`}}Copy the code
TS generally implements polymorphism for abstract methods, as described below in abstract classes.
public
All methods and properties in a class are public by default.
For example, the Person class defined above looks like this:
class Person {
public name: string
public constructor(name: string) {
this.name = name
}
public speak() {
console.log(`The ${this.name} is speaking`)}}Copy the code
Public Specifies whether to write or not to write.
private
Private, which belongs only to the class itself and is not accessible by instances or subclasses that inherit it.
Change the Name attribute of the Person class to private.
class Person {
private name: string
public constructor(name: string) {
this.name = name
}
public speak() {
console.log(`The ${this.name} is speaking`)}}Copy the code
If an instance accesses the name attribute, an error is reported:
If a subclass accesses the name attribute, an error is reported:
protected
Protected, accessible by subclasses that inherit it, not by instances.
Make the Name attribute of the Person class protected.
class Person {
protected name: string
public constructor(name: string) {
this.name = name
}
public speak() {
console.log(`The ${this.name} is speaking`)}}Copy the code
If an instance accesses the name attribute, an error is reported:
Subclasses are accessible.
class Studeng extends Person {
study() {
console.log(`The ${this.name} needs study`)}}Copy the code
static
Static is a constant on a class. Instances and subclasses cannot access static.
For example, a Circle class with PI of 3.14 can define a static property directly.
class Circle {
static pi: 3.14
public radius: number
public constructor(radius: number) {
this.radius = radius
}
public calcLength() {
return Circle.pi * this.radius * 2 // To calculate the circumference, go directly to circle.pi}}Copy the code
Instance access, an error is reported:
An abstract class
Abstract classes, the name of which sounds like a very difficult concept to understand, are actually very simple.
TS enhances JS classes with public, private, and protected modifiers.
In fact, TS also extends a new concept of JS – abstract class.
An abstract class is one that can only be inherited, but cannot be instantiated, simple as that.
Abstract classes have two characteristics:
- Abstract classes are not allowed to be instantiated
- Abstract methods in abstract classes must be implemented by subclasses
Abstract classes are defined by an abstract keyword. Let’s use two examples to get a feel for two characteristics of abstract classes.
Abstract classes are not allowed to be instantiated
abstract class Animal {}
const a = new Animal()
Copy the code
< div style = “max-width: 100%; clear: both;
Abstract methods in abstract classes must be implemented by subclasses
abstract class Animal {
constructor(name:string) {
this.name = name
}
public name: string
public abstract sayHi():void
}
class Dog extends Animal {
constructor(name:string) {
super(name)
}
}
Copy the code
Animal sayHi = Animal sayHi = Animal sayHi = Animal sayHi
The correct usage is as follows,
abstract class Animal {
constructor(name:string) {
this.name = name
}
public name: string
public abstract sayHi():void
}
class Dog extends Animal {
constructor(name:string) {
super(name)
}
public sayHi() {
console.log('wang')}}Copy the code
Why are they called abstract classes?
Obviously, abstract class is a broad and abstract concept, not an entity. As in the example above, the concept of animal is very broad. Cats, dogs and lions are all animals, but animals are not good examples.
Officially, in object-oriented concepts, all objects are represented by classes, but conversely, not all classes are represented by objects. If a class does not contain enough information to represent a concrete object, such a class is an abstract class.
For example, the Animal class only has some properties and methods that animals have, but it does not specifically include the properties and methods of cats or dogs.
So the use of abstract classes is to define a base class, declare common properties and methods, and then inherit them.
The advantage of abstract classes is that they can extract commonalities from things, making it easier to reuse code.
Abstract methods and polymorphism
Polymorphism is one of the three basic characteristics of object orientation.
Polymorphism means that the parent class defines an abstract method, has different implementations in multiple subclasses, and runs with different subclasses corresponding to different operations, for example,
abstract class Animal {
constructor(name:string) {
this.name = name
}
public name: string
public abstract sayHi():void
}
class Dog extends Animal {
constructor(name:string) {
super(name)
}
public sayHi() {
console.log('wang')}}class Cat extends Animal {
constructor(name:string) {
super(name)
}
public sayHi() {
console.log('miao')}}Copy the code
Dog and Cat both inherit from Animal, and both Dog and Cat implement sayHi differently.
This type of
Next, we’ll introduce a special type, this.
Class member methods can return a this directly, which makes it easy to implement chained calls.
Chain calls
class StudyStep {
step1() {
console.log('listen')
return this
}
step2() {
console.log('write')
return this}}const s = new StudyStep()
s.step1().step2() // chain call
Copy the code
Flexible calls to subclass parent methods
When inherited, this can represent either a parent or a child type
class StudyStep {
step1() {
console.log('listen')
return this
}
step2() {
console.log('write')
return this}}class MyStudyStep extends StudyStep {
next() {
console.log('before done, study next! ')
return this}}const m = new MyStudyStep()
m.step1().next().step2().next() // Methods on parent and child types can be called at will
Copy the code
This preserves the consistency of interface calls between the parent and subclasses
Interface and class
As mentioned above, interface is designed by TS to define object types and describe the shapes of objects.
Interface can also be used to constrain classes. To implement the constraint, you need the implements keyword.
implements
Implements interface; class implements interface.
My cell phone can play music.
interface MusicInterface {
playMusic(): void
}
class Cellphone implements MusicInterface {
playMusic(){}}Copy the code
Once a constraint is defined, the class must satisfy all conditions on the interface.
An error is reported if the playMusic method is not written on the Cellphone class.
Handles common properties and methods
Different classes have some common properties and methods that are difficult to accomplish using inheritance.
For example, cars can also play music. You can do this:
- Using the Car class, inherit the Cellphone class
- Find a parent of the Car and Cellphone classes, which inherit from the parent class that plays music
Obviously neither approach makes sense.
In fact, with implements, the problem goes away.
interface MusicInterface {
playMusic(): void
}
class Car implements MusicInterface {
playMusic(){}}class Cellphone implements MusicInterface {
playMusic(){}}Copy the code
In this way, the Car and Cellphone classes constrain the music playback function.
Another example is that mobile phones also have the function of making phone calls.
interface MusicInterface {
playMusic(): void
}
interface CallInterface {
makePhoneCall(): void
}
class Cellphone implements MusicInterface.CallInterface {
playMusic() {}
makePhoneCall(){}}Copy the code
This CallInterface can also be used for iPad classes, watches, after all, they can also make phone calls.
Interface constrains a class, as long as the class implements the properties or methods specified by interface.
Constraint constructors and static properties
Implements can only constrain properties and methods on class instances; to constrain constructors and static properties, you need to do so.
Take the Circl class we mentioned above for example:
interface CircleStatic {
new (radius: number) :void
pi: number
}
const Circle:CircleStatic = class Circle {
static pi: 3.14
public radius: number
public constructor(radius: number) {
this.radius = radius
}
}
Copy the code
If static PI is not defined, an error is reported:
Error () error ();
The enumeration
In any project, we will have to define constants, which are values that do not change.
In TS we use const to declare constants, but some values are a set of constants within a range, such as seven days in a week, or directions up, down, left, and right.
At this point, you can use enumeration (Enum) to define.
The basic use
enum Direction {
Up,
Down,
Left,
Right
}
Copy the code
This defines a numeric enumeration that has two characteristics:
- Digital incremental
- Reverse mapping
Enumerators are assigned incrementing numbers starting from 0,
console.log(Direction.Up) / / 0
console.log(Direction.Down) / / 1
console.log(Direction.Left) / / 2
console.log(Direction.Right) / / 3
Copy the code
Enumeration reversely maps enumeration values to enumeration names,
console.log(Direction[0]) // Up
console.log(Direction[1]) // Down
console.log(Direction[2]) // Left
console.log(Direction[3]) // Right
Copy the code
If the enumeration first element has an initial value, it increments from the initial value,
enum Direction {
Up = 6,
Down,
Left,
Right
}
console.log(Direction.Up) / / 6
console.log(Direction.Down) / / 7
console.log(Direction.Left) / / 8
console.log(Direction.Right) / / 9
Copy the code
Principle of reverse mapping
To see how enumerations do this, let’s look at the compiled code,
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 6] = "Up";
Direction[Direction["Down"] = 7] = "Down";
Direction[Direction["Left"] = 8] = "Left";
Direction[Direction["Right"] = 9] = "Right";
})(Direction || (Direction = {}));
Copy the code
The body code is wrapped in a self-executing function that encapsulates its own unique scope.
Direction["Up"] = 6
Copy the code
The Up attribute of the Direction object is assigned to 6, and the JS assignment operator returns the assigned value.
Direction["Up"] = 6return6
Copy the code
Executive Direction [Direction ["Up"] = 6] = "Up"; Equivalent to executing Direction["Up"] = 6
Direction[6] = "Up"
Copy the code
This enables reverse mapping of enumerations.
Manual assignment
Define an enumeration to manage the status of takeout: ordered, shipped, received.
I could write it this way,
enum ItemStatus {
Buy = 1,
Send,
Receive
}
console.log(ItemStatus['Buy']) / / 1
console.log(ItemStatus['Send']) / / 2
console.log(ItemStatus['Receive']) / / 3
Copy the code
But sometimes the state of the data returned to you by the back end is messy, so we need to manually assign values.
If the back end says Buy is 100, Send is 20, Receive is 1,
enum ItemStatus {
Buy = 100,
Send = 20,
Receive = 1
}
console.log(ItemStatus['Buy']) / / 100
console.log(ItemStatus['Send']) / / 20
console.log(ItemStatus['Receive']) / / 1
Copy the code
Don’t ask why, this happens all the time in real development.
Calculated members
The members of an enumeration can be evaluated, for example, classically using bitwise operations to merge permissions, by writing,
enum FileAccess {
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
}
console.log(FileAccess.Read) // 2 -> 010
console.log(FileAccess.Write) // 4 -> 100
console.log(FileAccess.ReadWrite) / / 6-110 >
Copy the code
For example, Vue3 source code patchFlags, used to identify node update properties.
// packages/shared/src/patchFlags.ts
export const enum PatchFlags {
TEXT = 1.// Dynamic text node
CLASS = 1 << 1./ / dynamic class
STYLE = 1 << 2./ / dynamic style
PROPS = 1 << 3.// Dynamic attributes
FULL_PROPS = 1 << 4.// Has a dynamic key property. When the key changes, a full diff comparison is required
HYDRATE_EVENTS = 1 << 5.// A node with listening events
STABLE_FRAGMENT = 1 << 6.// Fragment that does not change the order of child nodes
KEYED_FRAGMENT = 1 << 7.// Fragment with key genus or part of a node with key
UNKEYED_FRAGMENT = 1 << 8.// The child node does not have a key fragment
NEED_PATCH = 1 << 9.// comparison of non-props, such as ref or instruction
DYNAMIC_SLOTS = 1 << 10.// Dynamic slot
DEV_ROOT_FRAGMENT = 1 << 11.// Used for development purposes only to indicate that comments are placed in a template root-level fragment
HOISTED = -1.// Static node
BAIL = -2 // The diff algorithm should exit optimization mode
}
Copy the code
String enumeration
The point of string enumeration is to provide strings with concrete semantics that make it easier to understand code and debug.
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",}const value = 'UP'
if (value === Direction.Up) {
// do something
}
Copy the code
Constant enumeration
In the previous example, we used const to define a constant enumeration
const enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",}const value = 'UP'
if (value === Direction.Up) {
// do something
}
Copy the code
Compiled JS code will be much more concise, improved performance.
const value = 'UP';
if (value === 'UP' /* Up */) {
// do something
}
Copy the code
Instead of writing const, it compiles like this,
var Direction;
(function (Direction) {
Direction["Up"] = "UP";
Direction["Down"] = "DOWN";
Direction["Left"] = "LEFT";
Direction["Right"] = "RIGHT";
})(Direction || (Direction = {}));
const value = 'UP';
if (value === Direction.Up) {
// do something
}
Copy the code
The stack of logic that defines the enumeration is removed at compile time and the constant enumeration members are inlined where they are used.
Obviously, constant enumerations are not allowed to contain computed members, otherwise what is a constant?
const enum Test {
A = "lin".length
}
Copy the code
Error:
In summary, constant enumerations avoid the overhead of extra generated code and additional indirect access to enumeration members.
summary
The point of enumerations is that you can define collections of named constants that clearly express intent and semantics, making it easier to understand code and debug.
It is often used to distinguish the numbers or strings that represent the state semantics returned by the back end when interworking with the back end, reducing the mental burden when reading the code.
Type inference
In TypeScript, type inference helps provide types where they are not explicitly specified.
This inference occurs when initializing variables and members, setting default parameter values, and determining the return value of a function.
Definition without assignment
let a
a = 18
a = 'lin'
Copy the code
If no value is assigned, TS automatically deducts the value to any, and then any value is assigned without error.
Initialize a variable
Such as:
let userName = 'lin'
Copy the code
Since the assignment is a string, TS automatically deduces that userName is a string.
Select * from ‘userName’ where userName = ‘string’;
Set default parameter values
There is also an automatic derivation when the function sets default parameters
For example, define a function that prints the age. The default value is 18
function printAge(num = 18) {
console.log(num)
return num
}
Copy the code
The TS will automatically deduce the input type of printAge. If the input type is incorrect, an error will be reported.
Determines the return value of the function
When determining the return value of a function, TS also automatically deduces the return value type.
For example, if a function does not write a return value,
function welcome() {
console.log('hello')
}
Copy the code
TS automatically deduces that the return value is of type void
In the printAge function, TS automatically deduces that the return value is number.
Let’s see what happens if we make the return value of printAge a string.
function printAge(num = 18) {
console.log(num)
return num
}
interface PrintAge {
(num: number) :string
}
const printAge1: PrintAge = printAge
Copy the code
Int TS (int t, int t, int t, int t, int t, int t)
Best generic type
When you need to infer a type from several expressions, the types of those expressions are used to infer the most appropriate generic type. For instance,
let arr = [0.1.null.'lin'];
Copy the code
Such as:
let pets = [new Dog(), new Cat()]
Copy the code
Although TS can derive the most appropriate type, it is best to define the type at the time of writing.
type arrItem = number | string | null
let arr: arrItem[] = [0.1.null.'lin'];
let pets: Pets[] = [new Dog(), new Cat()]
Copy the code
summary
Type corollaries can help us, but since we’re writing TS, unless we’re using a function that returns void by default, which we all know, it’s best to define the type everywhere.
Built-in types
There are many built-in objects in JavaScript that can be treated directly as defined types in TypeScript.
Built-in objects are objects that exist in the global scope according to the standard, which in this case refers to the standard of ECMAcript and other environments, such as DOM.
JS has eight built-in types
let name: string = "lin";
let age: number = 18;
let isHandsome: boolean = true;
let u: undefined = undefined;
let n: null = null;
let obj: object = {name: 'lin'.age: 18};
let big: bigint = 100n;
let sym: symbol = Symbol("lin");
Copy the code
ECMAScript built-in object
For example, Array, Date, Error, etc.
const nums: Array<number> = [1.2.3]
const date: Date = new Date(a)const err: Error = new Error('Error! ');
const reg: RegExp = /abc/;
Math.pow(2.9)
Copy the code
In the case of Array, hold down COMand/CTRL and click with the left mouse button to jump to the type declaration.
As you can see, the Array type is defined by interface and is declared in several different versions of the.d.ts file.
In TS, repeating an interface declaration merges all of the declarations, and all of the.d. TS files merge the Array interfaces into all of the properties and functions of the Array built-in types.
Here’s another example
DOM and BOM
HTMLElement, NodeList, MouseEvent, etc
let body: HTMLElement = document.body
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', (e: MouseEvent) => {
e.preventDefault()
// Do something
});
Copy the code
TS core library definition file
The TypeScript core library definition files define the types needed by all browser environments and are preconfigured in TypeScript.
For example, math.pow’s type definition is as follows,
interface Math {
/**
* Returns the value of a base expression taken to a specified power.
* @param x The base value of the expression.
* @param y The exponent value of the expression.
*/
pow(x: number.y: number) :number;
}
Copy the code
For example, the addEventListener type is defined as follows,
interface Document extends Node, GlobalEventHandlers, NodeSelector, DocumentEvent {
addEventListener(type: string.listener: (ev: MouseEvent) = > any, useCapture? :boolean) :void;
}
Copy the code
It takes a lot of effort to actually analyze some type implementations of Web apis, just to know where to define them.
TS advanced
Advanced Type (1)
Advanced types have one and two parts, the one part can be understood without understanding generics, and the two part can be understood without understanding generics, so the two part is split later.
The joint type
If you want a variable to support multiple types, you can define it with union types.
For example, a variable that supports both number and string could be written like this:
let num: number | string
num = 8
num = 'eight'
Copy the code
Union types greatly improve type extensibility, but when TS is not sure what type a union type variable is, they can only access their common properties and methods.
For example, you can only access methods that are common to number and string.
If you access the length property directly, it’s a string property, but it’s not a number property,
Cross type
If you want to extend object shapes, you can use the cross type &.
Person has a name and age attribute, Student has a grade attribute on top of name and age,
interface Person {
name: string
age: number
}
type Student = Person & { grade: number }
Copy the code
This is exactly the same as class inheritance, so Student inherits the property on Person,
Joint type | means can take one of several types, and the cross type & refers to combine several types.
The cross type is very similar to the extends of an interface in that it is used to combine and extend object shapes.
Type Alias (Type)
Type aliases, as they sound, give types individual names.
Like giannis Antetokounmpo, the NBA player whose name is too long to remember, we call him Giannis antetokounmpo.
Just like configuring alias in our project, it is easy to import files without writing relative paths
import componentA from '.. /.. /.. /.. / components/component/index. Vue 'become an import component from' @ / components/component/index. The vueCopy the code
Type aliases are written with the type keyword. With type aliases, we can write TS more succinctly.
For example, in this example, the getName function may take a string or a function.
type Name = string
type NameResolver = () = > string
type NameOrResolver = Name | NameResolver // Union type
function getName(n: NameOrResolver) :Name {
if (typeof n === 'string') {
return n
}
else {
return n()
}
}
Copy the code
This calls both the string and the function.
getName('lin')
getName(() => 'lin')
Copy the code
If the format of the message is not correct, it will prompt you.
A type alias gives a type a new name. Type aliases are sometimes similar to interfaces, but can work with primitive values, union types, tuples, and any other type you need to write by hand. – TS document
Type aliases are used as follows,
type Name = string // Basic type
type arrItem = number | string // Union type
const arr: arrItem[] = [1.'2'.3]
type Person = {
name: Name
}
type Student = Person & { grade: number } // Cross types
type Teacher = Person & { major: string }
type StudentAndTeacherList = [Student, Teacher] // Tuple type
const list:StudentAndTeacherList = [
{ name: 'lin'.grade: 100 },
{ name: 'liu'.major: 'Chinese'}]Copy the code
Difference between Type and interface
In this example, you can use type or interface.
interface Person {
name: string
age: number
}
const person: Person = {
name: 'lin',
age: 18
}
Copy the code
type Person = {
name: string
age: number
}
const person: Person = {
name: 'lin',
age: 18
}
Copy the code
You can use either type or interface.
Similarities:
- Can define an object or a function
- Both allow inheritance
Can define an object or a function
Now that we’ve talked about defining objects, let’s see how to define functions.
type addType = (num1:number,num2:number) = > number
interface addType {
(num1:number.num2:number) :number
}
// The function type can be defined either way
Copy the code
const add:addType = (num1, num2) = > {
return num1 + num2
}
Copy the code
Both allow inheritance
We define a Person type and a Student type. Student inherits from Person in the following four ways
// interface inherits interface
interface Person {
name: string
}
interface Student extends Person {
grade: number
}
Copy the code
const person:Student = {
name: 'lin',
grade: 100
}
Copy the code
// type inherits type
type Person = {
name: string
}
type Student = Person & { grade: number } // Use the cross type
Copy the code
// interface inherits type
type Person = {
name: string
}
interface Student extends Person {
grade: number
}
Copy the code
// type inherits interface
interface Person {
name: string
}
type Student = Person & { grade: number } // Use the cross type
Copy the code
Interface uses extends for inheritance, and type uses cross types for inheritance
Differences between the two:
-
Interface is designed by TS to define the type of object, which can describe the shape of the object.
-
Type is a type alias used to define aliases for various types, making TS easier and clearer to write.
-
Type can declare basic types, union types, cross types, and tuples, but interface cannot
-
Interface can merge duplicate declarations, but type cannot
Merge duplicate declaration:
interface Person {
name: string
}
interface Person { // repeat the declaration of interface, then merge
age: number
}
const person: Person = {
name: 'lin'.age: 18
}
Copy the code
If type is declared repeatedly, an error is reported
type Person = {
name: string
}
type Person = { // Duplicate identifier 'Person'
age: number
}
const person: Person = {
name: 'lin',
age: 18
}
Copy the code
The difference between the two said so much, in fact, should not be used to compare the two things, they are completely different concepts.
Interface is an interface that describes an object.
Type is a type alias used to define aliases for various types, making TS easier and clearer to write.
It is only sometimes that both achieve the same function that they are often confused
In normal development, use type when using composite or crossover types.
When you want to use an extends or implements class, use an interface.
Other cases, such as defining an object or function, depend on your mood.
Type of protection
If there is a getLength function, into ginseng is joint type number | string, back into the length,
function getLength(arg: number | string) :number {
return arg.length
}
Copy the code
The number type does not have a length attribute.
This is where Type Guards come in, and you can use the Typeof keyword to determine the Type of a variable.
Let’s change the getLength method to get the exact length of a string,
function getLength(arg: number | string) :number {
if(typeof arg === 'string') {
return arg.length
} else {
return arg.toString().length
}
}
Copy the code
The reason it’s called type protection is to be able to narrow down the scope of different branching conditions so that our code is much less likely to go wrong.
Types of assertions
The previous example can also be addressed using type assertions.
Type assertion syntax:
valueastypeCopy the code
Use type assertions to tell TS that I (the developer) know what type this parameter is better than you (the compiler), so don’t give me an error,
function getLength(arg: number | string) :number {
const str = arg as string
if (str.length) {
return str.length
} else {
const number = arg as number
return number.toString().length
}
}
Copy the code
Note that type assertion is not a type conversion, and asserting a type to a type that does not exist in the union type raises an error.
For instance,
function getLength(arg: number | string) :number {
return (arg as number[]).length
}
Copy the code
Literal type
Sometimes, when we need to define constants, we need to use literal types, for example,
type ButtonSize = 'mini' | 'small' | 'normal' | 'large'
type Sex = 'male' | 'woman'
Copy the code
So you can only take values from these constants that are defined, and you get an error if you don’t take values,
The generic
Generics is the difficult part of TS. With generics, the understanding of TS will be further improved, which is of great help to the follow-up in-depth study.
Why generics?
If you’ve seen the TS documentation, you’ve probably seen the following two paragraphs:
In software engineering, it is important not only to create consistent, well-defined apis, but also to consider reusability. The ability of components to support not only current data types but also future data types gives you a lot of flexibility when building large systems.
In languages like C# and Java, generics can be used to create reusable components that can support multiple types of data. This allows users to use components with their own data types.
It’s not human language. Are you sure beginners can understand it?
I think it’s important for beginners to understand why generics are needed and what problems do they solve? Instead of looking at this tricky definition.
Let’s take a look at an example of the problem generics solve.
Print print print print print print print print print print print print print print print
function print(arg:string) :string {
console.log(arg)
return arg
}
Copy the code
Now the requirements have changed and I also need to print the number type, what do I do?
Can be modified using union types:
function print(arg:string | number) :string | number {
console.log(arg)
return arg
}
Copy the code
Now that the requirements have changed, I also need to print an array of strings, an array of numbers, or any type, what do I do?
There is a silly way to write as many union types as you can support.
Or change the parameter type to any.
function print(arg:any) :any {
console.log(arg)
return arg
}
Copy the code
Not to mention that the any type is bad, after all, try not to write any in TS.
And that’s not what we want, we can just say that the incoming value is of type ANY and the output value is of type ANY, and the incoming and returned values are not uniform.
It’s even buggy
const res:string = print(123)
Copy the code
We define a string to receive the return value of print. The return value is a number. TS does not give us an error message.
This is where generics come in, which can easily solve the problem of consistent input and output.
Note: Generics were not designed to solve this one problem. Generics solve many other problems as well.
Generics are basic
Processing function parameters
We use generics to solve the problem above.
The syntax for generics is to write type parameters in <>, usually represented by T.
function print<T> (arg:T) :T {
console.log(arg)
return arg
}
Copy the code
In this way, we achieve the same type of input and output, and can input and output any type.
If the types are inconsistent, an error is reported:
A T in a generic type is like a placeholder, or a variable, that can be used to pass in the defined type as an argument, and it can be printed unchanged.
The way generics are written is a little odd to front-end engineers, such as <> T, but remember that when you see <>, you know it’s a generic.
We can specify types in two ways when we use them.
- Define the type to use
- TS type inference, automatically deduce the type
print<string> ('hello') // define T as string
print('hello') // TS type inference, automatic inference type is string
Copy the code
We know that both type and interface can define function types. We can also use generics to write type like this:
type Print = <T>(arg: T) = > T
const printFn:Print = function print(arg) {
console.log(arg)
return arg
}
Copy the code
Interface:
function print<T> (arg:T) :T {
console.log(arg)
return arg
}
interface Iprint<T> {
(arg: T): T
}
const myPrinnt: Iprint<number> = print
Copy the code
The default parameters
To add a default parameter to a generic, write:
function print<T> (arg:T) :T {
console.log(arg)
return arg
}
interface Iprint<T = number> {
(arg: T): T
}
const myPrinnt: Iprint = print
Copy the code
So the default type is number, ok? Does T feel like a function argument?
Process multiple function parameters
Now there is a function that passes in a tuple with only two items, swaps the 0th and 1st items of the tuple, and returns the tuple.
function swap(tuple) {
return [tuple[1], tuple[0]]}Copy the code
So if we write it this way, we’re going to lose the type, and we’re going to rewrite it with generics.
We use T for the type of the 0th term and U for the type of the first term.
function swap<T.U> (tuple: [T, U]) :U.T]{
return [tuple[1], tuple[0]]}Copy the code
This allows control of the 0th and 1st items of the tuple.
In the argument passed in, the 0th item is of type string and the first item is of type number.
In the return value of the swap function, the 0th item is of type number and the first item is of type string.
The zeroth term is full of number methods.
The first item is full of string methods.
Function side effect operation
Generics can be used not only to easily constrain the parameter types of functions, but also when functions perform side effects.
For example, we have a generic asynchronous request method that wants to return different types of data for different URL requests.
function request(url:string) {
return fetch(url).then(res= > res.json())
}
Copy the code
Call an interface to get user information:
request('user/info').then(res= >{
console.log(res)
})
Copy the code
The result, res, is of type any, which is annoying.
We want the calling API to clearly know what data structure the return type is. We can do this:
interface UserInfo {
name: string
age: number
}
function request<T> (url:string) :Promise<T> {
return fetch(url).then(res= > res.json())
}
request<UserInfo>('user/info').then(res= >{
console.log(res)
})
Copy the code
In this way, you can easily get the data type returned by the interface, which greatly improves the development efficiency:
Constraints of generic
Suppose we now have a function that prints the length of the argument passed in. Let’s write it like this:
function printLength<T> (arg: T) :T {
console.log(arg.length)
return arg
}
Copy the code
T has a length attribute.
So now I want to constrain this generic to have a length property, what do I do?
You can combine it with interface to constrain the type.
interface ILength {
length: number
}
function printLength<T extends ILength> (arg: T) :T {
console.log(arg.length)
return arg
}
Copy the code
The key is that
makes this generic extend the interface ILength so that it can constrain generics.
Variables we define must have length attributes, such as STR, arr, and obj below, to compile with TS.
const str = printLength('lin')
const arr = printLength([1.2.3])
const obj = printLength({ length: 10 })
Copy the code
This example again demonstrates duck typing of interfaces.
As long as you have the length property, it’s bound, it doesn’t matter if you’re STR, arr, obj.
Of course, if we define a variable that does not contain the length attribute, such as a number, we will get an error:
Some applications of generics
With generics, you can define a function, interface, or class without specifying a specific type up front. Instead, you specify the type when you use it.
Generics constrain classes
Define a stack with two methods: on and off the stack. If you want the elements on and off the stack to be of the same type, you can write:
class Stack<T> {
private data: T[] = []
push(item:T) {
return this.data.push(item)
}
pop():T | undefined {
return this.data.pop()
}
}
Copy the code
When defining an instance, write the type. For example, if both the stack and the stack are of type number, write:
const s1 = new Stack<number> ()Copy the code
This will cause an error when a string is pushed:
This is very flexible. If the requirements change, both the stack and the stack should be strings, which can be changed when defining instances:
const s1 = new Stack<string> ()Copy the code
This will cause an error when a number is pushed:
In particular, generics cannot constrain static members of a class.
It is an error to define the static keyword for the pop method
Generic constraint interfaces
Using generics, you can also modify an interface to make it more flexible.
interface IKeyValue<T, U> {
key: T
value: U
}
const k1:IKeyValue<number.string> = { key: 18.value: 'lin'}
const k2:IKeyValue<string.number> = { key: 'lin'.value: 18}
Copy the code
Generics define arrays
Define an array, as we wrote earlier:
const arr: number[] = [1.2.3]
Copy the code
Now I can also write this:
const arr: Array<number> = [1.2.3]
Copy the code
Array entry type error, error reported
summary
Generics, literally, Generics are general and general.
Generics are defined when a function, interface, or class is defined without specifying a specific type in advance. Instead, the type is specified when it is used.
A T in a generic type is like a placeholder, or a variable, that can be used to pass in the defined type as an argument, and it can be printed unchanged.
Generics provide meaningful constraints between members: function parameters, function return values, instance members of a class, methods of a class, and so on.
Here’s a diagram summarizing the benefits of generics:
! [image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bc01a49496f848e29c2fa8883ebcf5ec~tplv-k3u1fbpfcp-watermark .image?)Copy the code
Advanced Type (2)
The index type
I’m going to extract some property values from an object, and I’m going to concatenate them into an array, so I can say,
const userInfo = {
name: 'lin'.age: '18',}function getValues(userInfo: any, keys: string[]) {
return keys.map(key= > userInfo[key])
}
// Extract the value of the specified attribute
console.log(getValues(userInfo, ['name'.'age'])) // ['lin', '18']
// Extract attributes not found in obj:
console.log(getValues(userInfo, ['sex'.'outlook'])) // [undefined, undefined]
Copy the code
Although obj does not contain sex and Outlook attributes, the TS compiler does not report an error
At this point, TS index type is used to do type constraints on this situation, and dynamic attribute checking is realized.
Understanding index types requires an understanding of keyof (index query), T[K] (index access), and extends (generic constraint).
Keyof (index query)
The keyof operator can be used to get all keys of a type whose return type is the union type.
interface IPerson {
name: string;
age: number;
}
type Test = keyof IPerson; // 'name' | 'age'
Copy the code
In the example above, the Test type becomes a string literal.
T[K] (Index access)
T[K], represents the type represented by the attribute K of interface T,
interface IPerson {
name: string;
age: number;
}
let type1: IPerson['name'] // string
let type2: IPerson['age'] // number
Copy the code
Extends (Generic constraint)
T extends U, which means that a generic variable can inherit from a type and get some properties, and as we’ve seen before, just to review,
interface ILength {
length: number
}
function printLength<T extends ILength> (arg: T) :T {
console.log(arg.length)
return arg
}
Copy the code
STR, arr, obj, etc. Num does not.
const str = printLength('lin')
const arr = printLength([1.2.3])
const obj = printLength({ length: 10 })
const num = printLength(10) Argument of type 'number' is not assignable to parameter of type 'ILength'
Copy the code
Checking dynamic Properties
After understanding several concepts of index type, the getValue function is reformed to realize dynamic attribute checking on the object.
Before modification,
const userInfo = {
name: 'lin'.age: '18',}function getValues(userInfo: any, keys: string[]) {
return keys.map(key= > userInfo[key])
}
Copy the code
- Define generic T, K to constrain userInfo and keys
- Add a generic constraint to K so that K inherits the combined type of all userInfo attributes, i.e
K extends keyof T
After modification,
function getValues<T.K extends keyof T> (userInfo: T, keys: K[]) :T[K] []{
return keys.map(key= > userInfo[key])
}
Copy the code
So when we specify properties that are not in the object, we get an error,
Mapping type
TS allows you to map one type to another.
in
Before we get to map types, let’s look at the IN operator, which is used to iterate over union types.
type Person = "name" | "school" | "major"
type Obj = {
[p in Person]: string
}
Copy the code
Partial
Partial
maps all attributes of T to optional, for example:
interface IPerson {
name: string
age: number
}
let p1: IPerson = {
name: 'lin'.age: 18
}
Copy the code
If you use the IPerson interface, you have to pass the name and age attributes,
Use Partial to make it optional,
interface IPerson {
name: string
age: number
}
type IPartial = Partial<IPerson>
let p1: IPartial = {}
Copy the code
Partial principle
Partial implementations use in and keyof
/** * Make all properties in T optional */
type Partial<T> = {
[P inkeyof T]? : T[P]; };Copy the code
[P in keyof T]
traverseT
All properties of? :
Set the property to optionalT[P]
Set the type to the original type
Readonly
Readonly
maps all properties of T to read-only, for example:
interface IPerson {
name: string
age: number
}
type IReadOnly = Readonly<IPerson>
let p1: IReadOnly = {
name: 'lin'.age: 18
}
Copy the code
Readonly principle
Almost exactly like Partial,
/** * Make all properties in T readonly */
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
Copy the code
[P in keyof T]
traverseT
All properties ofreadonly
Set the property to optionalT[P]
Set the type to the original type
Pick
Pick is used to extract a subset of objects, Pick a set of attributes and form a new type, for example:
interface IPerson {
name: string
age: number
sex: string
}
type IPick = Pick<IPerson, 'name' | 'age'>
let p1: IPick = {
name: 'lin'.age: 18
}
Copy the code
This extracts the name and age from the IPerson.
Pick the principle
/** * From T, pick a set of properties whose keys are in the union K */
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
Copy the code
The Pick mapping type takes two parameters:
- The first parameter, T, represents the target object to be extracted
- The second argument, K, has one constraint: K must come from the union type of all property literals of T
Record
The above three mapping types are officially called homomorphisms, meaning that they only apply to obj properties and do not introduce new properties.
Record is a non-homomorphic mapping type that creates new properties.
interface IPerson {
name: string
age: number
}
type IRecord = Record<string, IPerson>
let personMap: IRecord = {
person1: {
name: 'lin'.age: 18
},
person2: {
name: 'liu'.age: 25}}Copy the code
Record the principle
/** * Construct a type with a set of properties K of type T */
type Record<K extends keyof any, T> = {
[P in K]: T;
}
Copy the code
The Record mapping type takes two parameters:
- The first argument can be passed in any value inherited from any
- The second argument, as the value of the newly created object, is passed in.
Conditions in the
T extends U ? X : Y
// If type T can be assigned to type U, the result type is type X, otherwise it is type Y
Copy the code
The implementation of Exclude and Extract uses conditional types.
Exclude
type Test = Exclude<'a' | 'b' | 'c'.'a'>;
Copy the code
Exclude principle
/** * Exclude from T those types that are assignable to U */
type Exclude<T, U> = T extends U ? never : T;
Copy the code
never
Represents a non-existent typenever
For other types after union with other types
type Test = string | number | never
Copy the code
Extract
Extract
Extract all intersection of union type T and union type U.
type Test = Extract<'key1' | 'key2'.'key1'>
Copy the code
Extract the principle
/** * Extract from T those types that are assignable to U */
type Extract<T, U> = T extends U ? T : never;
Copy the code
Exclude means Extract.
Utility Types
For developers’ convenience, TypeScript has some common tool types built in.
The index types, map types, and condition types described above are utility types.
In addition to the above introduction, I will introduce some commonly used tools and functions, after all, it is too boring to memorize by rote, practice makes perfect, write more natural familiar with.
Omit
Omit
to exclude all attributes in U from type T.
interface IPerson {
name: string
age: number
}
type IOmit = Omit<IPerson, 'age'>
Copy the code
This removes the age attribute from the IPerson.
Omit the principle
/** * Construct a type with the properties of T except for those in type K. */
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Copy the code
A Pick is used to select a group of attributes and form a new type, and a Omit one is used to Omit some attributes and leave the rest.
You can use Pick and Exclude to Omit anything.
You can also do it without picking,
type Omit2<T, K extends keyof any> = {
[P in Exclude<keyof T, K>]: T[P]
}
Copy the code
NonNullable
NonNullable
Filter null and undefined types.
type T0 = NonNullable<string | number | undefined>; // string | number
type T1 = NonNullable<string[] | null | undefined>; // string[]
Copy the code
NonNullable principle
/**
* Exclude null and undefined from T
*/
type NonNullable<T> = T extends null | undefined ? never : T;
Copy the code
never
Represents a non-existent typenever
For other types after union with other types
Parameters
Parameters takes the parameter type of a function, placing each parameter type in a tuple.
type T1 = Parameters<() = > string>; / / []
type T2 = Parameters<(arg: string) = > void>; // [string]
type T3 = Parameters<(arg1: string, arg2: number) = > void>; // [arg1: string, arg2: number]
Copy the code
Principle of the Parameters
/** * Obtain the parameters of a function type in a tuple */
type Parameters<T extends(... args:any) = >any> = T extends(... args: infer P) =>any ? P : never;
Copy the code
In conditional type statements, a type variable can be declared and used with infer.
Parameters
First constraint parameterT
Must be a function type- judge
T
Whether it is a function type, if soinfer P
Save the parameter type of the function for the moment. The following statements will retrieve this type and return it, or else return itnever
ReturnType
ReturnType Gets the return value type of the function.
type T0 = ReturnType<() = > string>; // string
type T1 = ReturnType<(s: string) = > void>; // void
Copy the code
ReturnType principle
/** * Obtain the return type of a function type */
type ReturnType<T extends(... args:any) = >any> = T extends(... args:any) => infer R ? R : any;
Copy the code
Parameters, also know ReturnType,
ReturnType
First constraint parameterT
Must be a function type- judge
T
Whether it is a function type, if soinfer R
Save the return value type of the function for the moment. The following statements will retrieve this type and return it, or else return itany
What is type gymnastics?
In this section, we’ve familiarized ourselves with the functions and principles of many tool types, and we’ve actually done some type gymnastics without realizing it
TypeScript advanced types evaluate new types based on type parameters, a process that involves a set of type calculation logic called type gymnastics. Of course, this is not a formal concept, just a community joke, because some types of computation logic is more complex.
If you think about all the tool types we’ve been looking at, all of them are evaluating types and returning new types.
Ts is a Turing-complete programming language, that is, the codification of types, which can generate specified types through code logic.
Personally feel TS type gymnastics this kind of thing, we who move bricks do not have to brush the algorithm as deliberately to train, a little taste, understand can, more can understand this article.
TS Declaration file
declare
When using third-party libraries, many third-party libraries are not written in TS, so we need to reference its declaration file to obtain the corresponding code completion, interface prompts and other functions.
For example, if you use Vue directly in TS, an error will be reported,
const app = new Vue({
el: '#app'.data: {
message: 'Hello Vue! '}})Copy the code
In this case, we can use the DECLARE keyword to define the type of Vue.
interface VueOption {
el: string.data: any
}
declare class Vue {
options: VueOption
constructor(options: VueOption)}const app = new Vue({
el: '#app',
data: {
message: 'Hello Vue! '}})
Copy the code
Using the DECLARE keyword tells the TS compiler that the type of the variable (Vue) has already been defined elsewhere, so you can use it without reporting an error.
Note that Declare Class Vue does not actually define a class, only the type of class Vue, which is only used for compile-time checks and will be deleted in the compile result. It compiles as follows:
const app = new Vue({
el: '#app'.data: {
message: 'Hello Vue! '}})Copy the code
.d.ts
Usually we put declarations in a separate file (vue.d.ts), which is the declaration file, with the suffix.d.ts.
// src/Vue.d.ts
interface VueOption {
el: string.data: any
}
declare class Vue {
options: VueOption
constructor(options: VueOption)}Copy the code
// src/index.ts
const app = new Vue({
el: '#app'.data: {
message: 'Hello Vue! '}})Copy the code
In general, TS parses all *. Ts files in a project, including those ending in.d.ts. So when we put vue.d. ts into the project, all the other *.ts files can get the Vue type definition.
The/path/to/project ├ ─ ─ the SRC | ├ ─ ─ but ts | └ ─ ─ Vue. Which s └ ─ ─ tsconfig. JsonCopy the code
Using the Tripartite library
So when we use tripartite libraries, do all the tripartite libraries have to write a bunch of decare files?
The answer is not necessarily, it depends on whether there is a TS type package for the tripartite library in the community (usually there is).
The community uses @types to centrally manage third-party library declaration files, managed by DefinitelyTyped
Like installing the LoDash type package,
npm install @types/lodash -D
Copy the code
Once installed, you can use LoDash normally in TS, and you don’t need to do anything else.
Of course, if a library is written in TS, you don’t have to worry about type files, such as Vue3.
Write your own declaration
For example, if you wrote a request module called myFetch, the code is as follows:
function myFetch(url, method, data) {
return fetch(url, {
body: data ? JSON.stringify(data) : ' ',
method
}).then(res= > res.json())
}
myFetch.get = (url) = > {
return myFetch(url, 'GET')
}
myFetch.post = (url, data) = > {
return myFetch(url, 'POST', data)
}
export default myFetch
Copy the code
Now that the new project uses TS, to continue using myFetch in the new project, you have two options:
- Rewrite myFetch with TS, the new project quotes the rewritten myFetch
- Reference myFetch directly and write a declaration file to it
If you choose option two, you can do this,
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
declare function myFetch<T = any> (url: string, method: HTTPMethod, data? :any) :Promise<T>
declare namespace myFetch { // Use a namespace to declare properties and methods under an object
const get: <T = any>(url: string) => Promise<T>
const post: <T = any>(url: string, data: any) => Promise<T>
}
Copy the code
The tricky thing is that you need to configure it, and there are two options,
- To create a
node_modules/@types/myFetch/index.d.ts
File, storagemyFetch
Module declaration file. No additional configuration is required, howevernode_modules
The directory is not stable, the code is not saved in the repository, the version can not be backtracked, and there is a risk of accidentally deleted, so this solution is not recommended, generally only for temporary testing. - To create a
types
Directory, dedicated to managing self-written declaration files, willmyFetch
The declaration file is placed intypes/myFetch/index.d.ts
In the. This mode needs to be configuredtsconfig.json
In thepaths
和baseUrl
Field.
// tsconfig.json
{
"compilerOptions": {
"module": "commonjs"."baseUrl": ". /"."paths": {
"*": ["types/*"]}}}Copy the code
Let’s test it out, call it in,
Return value alert,
The way to abbreviate,
It feels better to rewrite directly in TS than to write declarations for older projects without having to maintain type modules.
Many libraries used in normal development are now basically rewritten in TS.
This article only covers the tip of the iceberg of TS documentation, but more on the official documentation.
Feel that the official document is difficult to chew can also read this article, but the knowledge point is also a lot, also difficult to chew.
Learn the knowledge of this article, start the project certainly no problem, can work on the line, learn too much hair will fall light!
TS of actual combat
Vue3 todoList
By writing a Vue3 todoList, I am familiar with the writing method of adding, deleting, modifying and checking most commonly used in daily work, familiar with the most basic Vue3 grammar, and experience the relatively simple writing method of TS combined with Vue3
Related to knowledge
- Vue3
- script setup
- ref
- computed
- Conditional and list rendering
- Data binding and V-Model
- The event
- TS
- The base type
- interface
- The generic
- TS combination Vue3
The functionality to be implemented
- Add to-do items
- Delete the to-do list
- Select all and deselect all
- Clean up your to-do list
Project installation
Create a new Vite project using PNPM
pnpm create vite my-v3-app -- --template vue
Copy the code
Then enter the project, install the dependencies, and start
cd my-v3-app
pnpm i
pnpm dev
Copy the code
It was so fast, it took less than a minute to start developing todoList.
Code implementation
<template>
<input type="text" v-model="todoMsg">
<button @click="add">add</button>
<button @click="clearHasDone">Clean up the</button>
<div v-if="lists.length">
<div v-for="(item, index) in lists" :key="item.msg">
<input type="checkbox" v-model="item.done">
<span :class="{done: item.done}">{{item.msg}}</span>
<span @click="deleteItem(index)">❎</span>
</div>
<div>
<span>select all</span>
<input type="checkbox" v-model="isAllDone">
<span>{{hasDown}} / {{lists.length}}</span>
</div>
</div>
<div v-else>Temporarily no data</div>
</template>
Copy the code
<script lang='ts' setup>
import { ref, computed } from 'vue';
interface TodoItem { // Define the toDO item type
msg: string // Something to do
done: boolean // Whether to complete
}
const todoMsg = ref<string> (' ') // Use a ref to define toDO items and bind them bidirectionally
const lists = ref<TodoItem[]>([ // Define a todo list to initialize some data
{ msg: 'eat'.done: true},
{ msg: 'sleep'.done: false},
{ msg: 'Play games'.done: false}])const hasDown = computed(() = > lists.value.filter(item= > item.done).length) // List of things that have been done
const isAllDone = computed<boolean> ({// Whether all items are completed, bidirectional binding to the all button
get() { // isAllDone to bind data bidirectionally
return hasDown.value === lists.value.length
},
set(value:boolean) { // isAllDone change method, used to implement all and cancel all function
lists.value.forEach(item= > {
item.done = value
})
}
})
const add = () = > { // New item
if (todoMsg.value) { // Add a value
lists.value.push({
msg: todoMsg.value,
done: false
})
todoMsg.value = ' ' // The input field is empty}}const deleteItem = (index:number) = > { // Delete items
lists.value.splice(index, 1)}const clearHasDone = () = > { // Clear up all completed items
lists.value = lists.value.filter(item= >! item.done) } </script>Copy the code
<style>
.done {
text-decoration: line-through; // Stripper stylecolor: gray;
}
</style>
Copy the code
summary
Although the implementation of todoList is very simple, but in daily development we write these things, commonly known as moving bricks, the USE of TS knowledge is not much, so ah, if just for daily development, really do not need to learn very deep.
Generics constrain the backend interface parameter types
Let’s look at a very useful use of generics for project development, constraining back-end interface parameter types.
import axios from 'axios'
interface API {
'/book/detail': {
id: number,},'/book/comment': {
id: number
comment: string}... }function request<T extends keyof API> (url: T, obj: API[T]) {
return axios.post(url, obj)
}
request('/book/comment', {
id: 1.comment: 'Great! '
})
Copy the code
There will be reminders when the interface is called, such as:
Wrong path:
The parameter type was passed incorrectly:
Parameter transmission is short:
Development efficiency improves greatly! You can finish up with the back end early and get your hands dirty.
React FunctionComponent
The React source code is said to be hard to read, but it’s not that scary. Let’s read the FunctionComponent type implementation in the React source code to help us understand TS.
First, define a functional component, UserInfo,
const UserInfo = (props) = > {
return (
<>
<h1>{props.name}</h1>
<p>{props.age}</p>
</>)}Copy the code
Props is of type any.
import { FunctionComponent } from 'react'
interface IUserInfo {
name: string.age: number
}
const UserInfo: FunctionComponent<IUserInfo> = (props: IUserInfo) = > {
return (
<>
<h1>{props.name}</h1>
<p>{props.age}</p>
</>)}Copy the code
Now that I have the properties defined by the interface on the props,
UserInfo now has methods on functions and properties defined on FunctionComponent
UserInfo also has interface defined properties on defaultProps and propTypes that are made optional,
How do these functions work? Let’s look into it together.
In VSCode, hold command/ CTRL and click on the FunctionComponent type to jump to the type definition,
interfaceFunctionComponent<P = {}> { (props: PropsWithChildren<P>, context? :any): ReactElement<any.any> | null; propTypes? : WeakValidationMap<P> |undefined; contextTypes? : ValidationMap<any> | undefined; defaultProps? : Partial<P> |undefined; displayName? :string | undefined;
}
typePropsWithChildren<P> = P & { children? : ReactNode |undefined };
type WeakValidationMap<T> = {
[K inkeyof T]? :null extends T[K]
? Validator<T[K] | null | undefined>
: undefined extends T[K]
? Validator<T[K] | null | undefined>
: Validator<T[K]>
};
Copy the code
This looks like a lot of code, but it’s all there is to it:
- Basic types: string, undefined, NULL, any
- Interface, optional attributes of interface, interface defines function type
- The joint type
- Generics. Generics constrain interfaces
- Tool types Partial, In, Keyof, extends
- Conditions in the
All are the above mentioned, when review again.
Such a piece of code, after careful analysis, can be summarized as the following figure (zoom in if you can’t see it clearly),
This figure is not summed up, but through this kind of thinking to analyze the type of source, no matter how much you can chew down.
TS encapsulates the AXIOS request
An excerpt from my article, using Taro + Vue3 to develop wechat small program, too much code, only post part, more details can visit github address clone use.
import { HttpResponse } from '@/common/interface'
import Taro from '@tarojs/taro'
import publicConfig from '@/config/index'
import axios, {
AxiosInstance,
AxiosRequestConfig,
AxiosResponse,
Canceler
} from 'axios-miniprogram'
import errorHandle from '.. /common/errorHandle'
const CancelToken = axios.CancelToken
class HttpRequest {
private baseUrl: string
private pending: Record<string, Canceler>
constructor(baseUrl: string) {
this.baseUrl = baseUrl
this.pending = {}
}
// Get the AXIOS configuration
getInsideConfig() {
const config = {
baseURL: this.baseUrl,
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
timeout: 10000
}
return config
}
removePending(key: string, isRequest = false) {
if (this.pending[key] && isRequest) {
this.pending[key]('Cancel duplicate request')}delete this.pending[key]
}
// Set interceptor
interceptors(instance: AxiosInstance) {
instance.interceptors.request.use(
config= > {
let isPublic = false
publicConfig.publicPath.map(path= > {
isPublic = isPublic || path.test(config.url || ' ')})const token = Taro.getStorageSync('token')
if(! isPublic && token) { config.headers.Authorization ='Bearer ' + token
}
const key = config.url + '&' + config.method
this.removePending(key, true)
config.cancelToken = new CancelToken(c= > {
this.pending[key] = c
})
return config
},
err= > {
errorHandle(err)
return Promise.reject(err)
}
)
// The interceptor that responds to the request
instance.interceptors.response.use(
res= > {
const key = res.config.url + '&' + res.config.method
this.removePending(key)
if (res.status === 200) {
return Promise.resolve(res.data)
} else {
return Promise.reject(res)
}
},
err= > {
errorHandle(err)
return Promise.reject(err)
}
)
}
// Create an instance
request(options: AxiosRequestConfig) {
const instance = axios.create()
const newOptions = Object.assign(this.getInsideConfig(), options)
this.interceptors(instance)
return instance(newOptions)
}
get(url: string, config? : AxiosRequestConfig):Promise<AxiosResponse> | Promise<HttpResponse> {
const options = Object.assign(
{
method: 'get'.url: url
},
config
)
return this.request(options)
}
post(url: string, data? : unknown):Promise<AxiosResponse> | Promise<HttpResponse> {
return this.request({
method: 'post'.url: url,
data: data
})
}
}
export default HttpRequest
Copy the code
request.ts
import HttpRequest from './http'
import config from '@/config/index'
const baseUrl = process.env.NODE_ENV === 'development' ? config.baseUrl.dev : config.baseUrl.prod
const request = new HttpRequest(baseUrl)
export default request
Copy the code
Take the example of getting a list of books and book details
apis/book.ts
import request from '.. /request'
export function getBookList() {
return request.get('books/getBookList')}export function getBookDetail(id: number) {
return request.post('books/getBookDetail', {
id
})
}
Copy the code
conclusion
Thank you very much for seeing my friends here. After reading this article and practicing it, you will definitely have no problem with the hands-on project.
However, after reading this article, you will also find that our TS study has just begun, there are too many in-depth knowledge that this article did not introduce, such as a lot of engineering knowledge, Alin level is limited, can not speak well.
A lot of knowledge is also from a variety of tutorials carried over, they have not fully mastered, this is the main purpose of my writing this article, to collect knowledge, forget when they have a look. ‘! 💪
If my article is helpful to you, your like 👍 is my biggest support ^_^
Refer to articles and tutorials
Is a reference article, is also the suggestion of follow-up study, these tutorials are fine, more detailed, more comprehensive.
A rare TS Study Guide (Po Ge)
TypeScript Tutorial xCATLiu
Geek Time – TypeScript Development
Imooc-vue3 + TS Imitation Zhihu Column Enterprise-level Project (Zhang Xuan)
A deep dive into the most esoteric advanced type tools in Ts