Due to the limitations of JS language itself, it is difficult to be competent for and maintain large projects. Therefore, Microsoft released the official version of TypeScript in 2013, enabling it to be competent for the development and maintenance of large projects. Now that many popular frameworks and libraries have moved to TypeScript development, what is the advantage of TS over JS? Let’s take a look at what TS uses to make the counterattack possible.
From Zero To Hero, let’s learn TS From Zero To Hero.
This article was first published in the public number [front-end one read], more exciting content please pay attention to the latest news of the public number.
Introduction of TypeScript
So what is TypeScript?
TypeScript is a superset of JavaScript types that can be compiled into pure JavaScript.
Here the superset is a mathematical concept, and its opposite is a subset; The so-called supersets and subsets, they come in pairs, there are supersets and there are subsets; TypeScript is a superset of JavaScript types, which means that TypeScript has all of the features and functions that JavaScript has, and some of the features and functions that JavaScript doesn’t have, to its advantage.
Graphically, it looks like this:
So what’s good about TypeScript? We all know that JavaScript is a weakly typed language, and it doesn’t have the same strict constraints on variable types as Java. This flexibility has allowed JavaScript to lower the barrier of entry and thrive, keeping it on the top of GitHub’s list of popular programming languages. On the other hand, flexibility can lead to uneven code quality, high maintenance costs, and run-time errors.
As you can see from the types in TypeScript’s name, the core feature of TypeScript is its type system, which compensates for JavaScript’s flexibility; So TypeScript has the following advantages over JavaScript:
- The type system enhances readability and maintainability of code;
- Errors can be found at compile time;
- Enhanced editor and IDE features, including code completion, interface hints, jump to definition, refactoring, etc.
- Compatibility allows existing JS code to work with TypeScript without modification;
All right, so much for rainbow farts, let’s get to the installation:
npm install -g typescript
Copy the code
After a global installation, we can compile our TypeScript files from anywhere using TSC commands, such as our common hello.ts:
function hello(person: string) {
return "Hello, " + person;
}
var user = "Jane User";
hello(user);
Copy the code
Then execute the compile command, and it becomes our common js:
tsc hello.ts
Copy the code
In the ts file above, use: to specify the type of the variable.
TypeScript basis
We’ll start with some basic TypeScript concepts to give you a basic understanding of TypeScript (ts).
Basic data types
Boolean value
Booleans are basic data types. In TS, Boolean is used to define Boolean types:
let isDone: boolean = false;
Copy the code
We need to distinguish between Boolean, which defines types, and Boolean, which is a constructor used to create objects:
let isDone: object = new Boolean(1);
let isDoneToo: boolean = Boolean(1);
Copy the code
Here, new Boolean() returns a Boolean object, which is essentially an object, while a Boolean() call can also return a Boolean value; Note the difference between the two, and the following basic data types also have such differences, which will not be repeated.
The numerical
let decLiteral: number = 6;
// Hexadecimal
let hexLiteral: number = 0xf00d;
/ / binary
let binaryLiteral: number = 0b1010;
/ / octal
let octalLiteral: number = 0o744;
Copy the code
Ts uses number to represent numeric types. In addition to decimal and hexadecimal values, ts also supports binary and octal numbers, which are compiled into binary numbers.
string
let firstName: string = 'Jack'
let lastName: string = "Bob"
let name: string = `${firstName} ${lastName}`
Copy the code
We use string to represent the text string type, either using single quotes (‘), double quotes (“), or template strings (‘) in ES6.
Void
In ts, void can be used to represent a function that does not return any value:
function helloTS() :void{
console.log("i have no return value")}Copy the code
It is useless to declare a variable of type void, we can only assign it undefined or null:
let a: void = undefined
// Error: cannot assign type 1 to type void.
a = 1
Copy the code
Never
The never type represents types that never have a value; For example, the never type is the return type of function expressions or arrow function expressions that always throw an exception or have no return value at all.
In addition, variables may also be of type never, when they are bound by type protection that is never true. To give you a better understanding of the never type, let’s use some practical examples.
// Define a variable of type never
let foo: never;
// A function that returns never must have an unreachable end
function error(message: string) :never {
throw new Error(message);
}
// The inferred return value type is never
function fail() {
return error("Something failed");
}
// A function that returns never must have an unreachable end
function infiniteLoop() :never {
while (true) {}}Copy the code
So what’s the use of the never type? You gave an example on Zhihu of using the never feature for a detailed check:
type Foo = string | number;
function handleValue(foo: Foo) {
if (typeof foo === "string") {
// here foo is narrowed to string
} else if (typeof foo === "number") {
// here foo is narrowed to number
} else {
// Never get there
const check: never= foo; }}Copy the code
Suppose we define a union type and do type determination in a function. If the logic is correct, then the last else can never be reached; But if one day your colleague changes the type of Foo:
type Foo = string | number;
Copy the code
If you forget to modify the control flow in the handleValue function, the check variable in the else will be narrowed to Boolean and a compilation error will occur.
In this way, we can exhaust all possible types of Foo, avoiding the possibility of adding a joint type with no corresponding implementation.
Unknown
When we write an application, we might need to describe a variable whose type we don’t know yet; These values can come from dynamic content, such as from the user, or we want to receive all possible types of values in the API. In these cases, we want to let the compiler and future users know that the variable can be of any type. At this point we will use the unknown type.
let maybe: unknown;
maybe = 1;
maybe = "2";
maybe = {};
maybe = [];
Copy the code
If you have a variable of type unknwon, you can narrow it down by typeof comparison or more advanced type checking:
let maybe: unknown;
if (typeof maybe === "string") {
maybe.substr(0.2);
} else if (typeof maybe === "number") {
maybe.toString();
} else if (typeof maybe === "boolean") {
maybe.valueOf();
}
Copy the code
Null, and Undefined
In TS, null and undefined can be used to define these two data types:
let u: undefined = undefined
let n: null = null
Copy the code
These two data types only define their own data and are not very useful by themselves; However, undefined and null are subtypes of all types, so they can be assigned to variables of other types, including void:
let num = undefined
Copy the code
Any
At programming time, we may not have determined the data type of a variable. The value may come from dynamic content, such as interface data, or a third-party library. We do not want to type check it, so we can use any to indicate that variable assignments are allowed to be of any data type.
let someValue:any = 10
someValue = '20'
someValue = {}
Copy the code
Can access any property and call any function at any value:
let someValue: any = ' '
console.log(someValue.name)
console.log(someValue.name.firstName)
someValue.showName()
someValue.name.showName()
Copy the code
A variable is recognized as an arbitrary value type if its type is not specified when it is declared:
let sth
/ / equivalent to the
// let sth:any
sth = "seven"
sth = 7
sth.setName("Tom")
Copy the code
Difference between Never and Void
Void Void Void Void Void Void Void Void Void Void Void Void Void Void Void
- Functions that do not return an explicit value implicitly return undefined. Although we usually talk about functions like this
Return nothing
But in fact it will return. In these cases, we usually ignore the return value. In TS the return type of these functions is inferred to bevoid
. - A function that returns type never
Never return
It also does not return undefined. The function did not complete properly, which means it mightAn exception is thrown
Or unable to exit execution at all.
The difference between “Unknow” and “Any
In TS, when we are not sure what type a type is, we can choose to declare it any or unkown. But in fact, TS recommends using unknown because it is type-safe.
If it is any, you can assign and value it arbitrarily, abandoning type checking entirely. However, the unknow type is different and must be narrowed to be evaluated.
let maybe1: any;
let maybe2: unknown
maybe1 = 1
maybe1 = {}
// It is running properly
maybe1.children.origin.hello()
maybe2 = 2
maybe2 = {}
/ / error:
// //Property 'length' does not exist on type 'unknown'
console.log(maybe2.length)
Copy the code
This reflects a fundamental difference between the two:
- Any is the parent and child of any type
- Unknown is the parent type of any type, but nothing more.
Type inference
In TS, if a variable is not explicitly typed, a type is inferred according to the rules of type inference.
let myNumber = 7
myNumber = '8'
Type 'string' is not assignable to Type 'number'.
Copy the code
Here the variable myNumber is inferred to be a number.
This article was first published in the public number [front-end one read], more exciting content please pay attention to the latest news of the public number.
The joint type
The joint type indicates that the value can be one of multiple types.
let myData: string | number
myData = 7
myData = "8"
Copy the code
We separate each type with a vertical line, allowing myData to be of type string or number, but not any other type.
Occasionally we will encounter a function passing a parameter that allows different types to be passed in, and we can also use a joint type:
function say(age: string | number){
console.log(age)
}
say(10)
say("11")
Copy the code
When ts is not sure which type a variable of the union type is, it can only access properties or methods that are common to all types of the union type:
function say(age: string | number){
ToString is a common method
console.log(age.toString())
// Error: Property 'length' does not exist on type 'number'.
console.log(age.length)
}
Copy the code
When a variable of the union type is assigned, it can infer a type from type inference:
let myData: string | number
myData = "8"
console.log(myData.length)
myData = 7
// Error: Property 'length' does not exist on type 'number'.
console.log(myData.length)
Copy the code
Array type
We can define arrays in ts in a number of ways.
Type and []
The simplest way to represent an array is to use the form of type +[] :
let list: number[] = [1.2.3]
Copy the code
No other types can appear in an array item, including a method that calls an array addition:
Type 'string' is not assignable to Type 'number'.
let list: number[] = [1.2.'3']
let list1: number[] = []
Argument of type 'string' is not assignable to parameter of type 'number'
list1.push('3')
Copy the code
For complex arrays, we can use any to indicate that any type is allowed in the array
let list: any[] = ['1'.2, { name: 'Lucy'.age: 18 }]
Copy the code
An array of generics
We can use array generics to represent arrays:
let list: Array<number> = [1.2.3]
Copy the code
More on generics below.
Interface said
We can also represent an array as an interface:
interface NumberArray {
[index: number] :number;
}
let list1: NumberArray = [1.2.3];
Copy the code
This is a cumbersome way to define an array, and is not often used, but we will use this form to represent an array of classes:
function sum() {
/ / error:
//Type 'IArguments' is missing the following properties from type 'any[]': pop, push, concat, join, and 15 more.
let arg: any[] = arguments;
}
Copy the code
Arguments (‘ push ‘, ‘pop’, ‘array’, ‘push’, ‘pop’, ‘push’, ‘pop’);
interface ArgumentInterface {
[index: number] :any;
length: number;
callee: Function;
}
function sum() {
let arg: ArgumentInterface = arguments;
}
Copy the code
ArgumentInterface here ArgumentInterface is actually defined inside TS, so we can use it directly:
function sum() {
let arg: IArguments = arguments;
}
Copy the code
The enumeration
Enumerations can be found everywhere in a project, such as a list of months, dates, or a list of options with background conventions. With TS, we can define enumerated values more clearly and conveniently.
enum Days {
Sun,
Mon,
Tue,
Wed,
Thu,
Fri,
Sat,
}
Copy the code
Enumerations are defined by enums. Enumerators are assigned incrementally from 0, and there is also a reverse mapping from enumeration values to enumerations:
console.log(Days['Sun'= = =0)
console.log(Days['Mon'= = =1)
console.log(Days['Tue'= = =2)
console.log(Days[0= = ='Sun')
console.log(Days[1= = ='Mon')
console.log(Days[2= = ='Tue')
Copy the code
In fact, the above enumeration code would compile into the following JS code:
var Days;
(function (Days) {
Days[Days["Sun"] = 0] = "Sun";
Days[Days["Mon"] = 1] = "Mon";
Days[Days["Tue"] = 2] = "Tue";
Days[Days["Wed"] = 3] = "Wed";
Days[Days["Thu"] = 4] = "Thu";
Days[Days["Fri"] = 5] = "Fri";
Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));
Copy the code
If we have additional requirements for enumeration values, we can do manual assignment:
enum Days {
Sun = 7,
Mon = 1,
Tue,
Wed,
Thu,
Fri,
Sat,
}
console.log(Days['Sun'= = =7)
console.log(Days['Mon'= = =1)
console.log(Days['Tue'= = =2)
console.log(Days['Wed'= = =4)
Copy the code
Enumerators that are not manually assigned are incremented by the value of the previous enumerator; Ts does not return an error if the manually assigned enumeration is the same as the subsequent increment:
enum Days {
Sun = 3,
Mon = 1,
Tue,
Wed,
Thu,
Fri,
Sat,
}
console.log(Days["Sun"= = =3); // true
console.log(Days["Wed"= = =3); // true
console.log(Days[3= = ="Sun"); // false
console.log(Days[3= = ="Wed"); // true
Copy the code
As you can see, when incrementing to 3, Wed is the same as Sun before, and the value of the enumeration continues to be retrieved. But the enumeration item corresponding to the enumeration value will be overwritten, and the above code will be compiled as follows:
var Days;
(function (Days) {
Days[Days["Sun"] = 3] = "Sun";
Days[Days["Mon"] = 1] = "Mon";
Days[Days["Tue"] = 2] = "Tue";
Days[Days["Wed"] = 3] = "Wed";
Days[Days["Thu"] = 4] = "Thu";
Days[Days["Fri"] = 5] = "Fri";
Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));
Copy the code
Enumerations of manually assigned values can also be decimal or negative, and subsequent assigned items are incremented by 1:
enum Days {
Sun = -2.5,
Mon,
Tue,
Wed = 3.5,
Thu,
Fri,
Sat,
}
console.log(Days['Mon'] = = = -1.5)
console.log(Days['Tue'] = = = -0.5)
console.log(Days['Thu'= = =4.5)
console.log(Days['Fri'= = =5.5)
Copy the code
Although eventually compiled into objects, the value of the enumeration item is not modifiable:
enum Days {
Sun,
Mon,
Tue,
}
/ / an error
// Cannot assign to 'Sun' because it is a read-only property.
Days.Sun = 7
Copy the code
String enumeration
Enumeration values can also be set to strings:
enum Days {
Sun = '0',
Mon = '1',
Tue = '2',
Wed = '3',}Copy the code
Since unset enumeration values are incrementing, we cannot set the middle enumeration value to a string so that the enumeration after it does not know where to start:
/ / error:
// Enum member must have initializer.
enum Days {
Sun,
Mon,
Tue = '2',
Wed = '3',
Thu,
Fri,
Sat,
}
// This is ok
enum Days1 {
Sun,
Mon,
Tue,
Wed,
Thu,
Fri,
Sat = '6',}Copy the code
Enumerations of strings are not bidirectionally mapped:
enum Days {
Sun = '0',
Mon = '1',
Tue = '2',
Wed = '3',}Copy the code
The above code will compile as follows:
var Days1;
(function (Days1) {
Days1["Sun"] = "0";
Days1["Mon"] = "1";
Days1["Tue"] = "2";
Days1["Wed"] = "3";
})(Days1 || (Days1 = {}));
Copy the code
Constant term and computed term
Enumeration items can be divided into constant items and calculation items. There are three cases of constant items:
- It is the first member of the enumeration and has no initializer
- It has no initializer and the enumerator before it is a numeric constant
- Enumerator use
Constant enumeration expression
Initialize the
All other cases are computed terms:
enum befor {
num,
}
enum test {
/ / constant terms
First,
Second = befor.num,
Add = 1 + 2,
NOR = 0 | 3,
OR = 1 & 2./ / item
LEN = "123".length,
RAM = Math.random()
}
Copy the code
Constant terms are computed at compile time and appear as constants in the code, which is compiled as follows:
var test;
(function (test) {
test[test["First"] = 0] = "First";
test[test["Second"] = 0] = "Second";
test[test["Add"] = 3] = "Add";
test[test["NOR"] = 3] = "NOR";
test[test["OR"] = 0] = "OR";
test[test["LEN"] = "123".length] = "LEN";
test[test["RAM"] = Math.random()] = "RAM";
})(test || (test = {}));
Copy the code
Constant enumeration
Constant enumerations are types of enumerations defined by const enum:
const enum Month {
Jan,
Feb,
Mar,
}
Copy the code
Constant enumerations are removed at compile time; When we do not need an object, but only the value of the object, we can use constant enumeration to avoid generating redundant code and indirect references at compile time:
const enum Month {
Jan,
Feb,
Mar,
}
/ / error:
//'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment or type query.
console.log(Month)
// Normal operation, output 0
console.log(Month.Jan);
Copy the code
Because constant enumerations are removed at compile time, constant enumerations cannot contain computed items:
const enum Month {
Jan,
// It is running properly
Feb = 1 + 2./ / error:
// const enum member initializers can only contain literal values and other computed enum values.
Mar = "123".length,
}
Copy the code
External enumeration
An external enumeration is an enumeration type defined using declare Enum:
declare enum Directions {
Up,
Down,
Left,
Right
}
Copy the code
External enumerations are as common in declaration files as declaration statements.
The type of the object – interface
In TS, we use interfaces to define the types of objects; This is sometimes called the duck discrimination:
A duck is one who walks, swims and quacks like a duck
That is, if a dog can walk, swim and quack like a duck, we consider it a duck.
Many children’s shoes may be difficult to understand, even if in fact there is a dog that walks and barks this way, it is also a dog, how can become a duck? Isn’t that typical of pointing fingers?
But if we put these two animals into the program,
function Duck(name){
this.name = name;
this.duckWalk = function(){
console.log("I am a duck and I walk happily.")}}function Dog(name){
this.name = name;
this.duckWalk = function(){
console.log("I am a duck and I walk happily.")}this.bark = function(){
console.log("I am a dog, and I bark happily.")}}var duck = new Duck("a")
var dog = new Dog("b")
// Drive the ducks forward
function needDuckWalk(duck){
duck.duckWalk()
}
needDuckWalk(duck)
needDuckWalk(dog)
Copy the code
There are two animal instances, Duck and dog. We need to drive the duck to walk forward (call their function), but the program does not need to distinguish which is which, as long as it can ensure that it has the duckWalk function. This is called duck discrimination.
Determine the attribute
In an object-oriented language, an interface is an abstraction of the behavior of a class to ensure that an object has the functions it needs.
interface Duck {
name: string;
age: number;
gender: boolean;
walk: Function;
}
let dog: Duck = {
name: "yellow".age: 2.gender: true.walk(){}};Copy the code
We define an interface Duck and specify that the literal dog is of type Duck, so that the property of dog must be consistent with the interface definition. If more or less properties are not allowed, an error will be reported.
/ / error:
// Property 'gender' is missing in type '{ name: string; age: number; }' but required in type 'Duck'.
let dog: Duck = {
name: "yellow".age: 2};Copy the code
Optional attribute
But there are some optional attributes that we don’t want to match exactly, so we can use optional attributes:
interface Duck {
name: string; age? :number; birth? :string; gender? :boolean;
}
let dog: Duck = {
name: "yellow"};Copy the code
Optional Attributes A question mark is added after an attribute to indicate that the attribute is optional. Multiple optional attributes can exist on an interface. However, other attributes are not allowed to be added.
Any attribute
We want to add arbitrary attributes to an interface, using arbitrary attributes:
interface Duck {
name: string; age? :number;
[propName: string] :any;
}
let dog: Duck = {
name: "yellow".birth: "".color: ["red"]};Copy the code
Note, however, that if we define any attributes, then the deterministic and optional attributes we define above must be subsets of their types:
/ / error:
// Property 'age' of type 'number' is not assignable to 'string' index type 'string'.
// Property 'gender' of type 'boolean' is not assignable to 'string' index type 'string'.
interface Duck {
name: string; age? :number; gender? :boolean;
[propName: string] :string;
}
Copy the code
Age and gender are number and Boolean types, respectively, and are not subsets of string. Only one arbitrary type can be defined in an interface. If the interface has attributes of more than one type, union types can be used:
interface Duck {
name: string; age? :number;
[propName: string] :string | number;
}
let dog: Duck = {
name: "yellow".birth: ""
};
Copy the code
Read-only property
Sometimes we want to have properties that can only be read and cannot be modified. We can define them as read-only properties, such as unique id information. Read-only attributes can only be assigned at initialization:
interface Duck {
readonly id: number;
name: string;
}
let dog: Duck = {
id: 9527.name: "yellow"};/ / error:
// Cannot assign to 'id' because it is a read-only property.
dog.id = 9528;
Copy the code
If I initialize an object without assigning it to it, I can change the value of the read-only property later.
interface Duck {
readonly id: number;
name: string;
}
/ / error:
// Property 'id' is missing in type '{ name: string; }' but required in type 'Duck'.
let dog: Duck = {
name: "yellow"};Copy the code
Unfortunately, you will get another mistake.
The properties of read-only attributes, like deterministic attributes, are indispensable; For those of you who are wondering, I’ll just add the optional property to the list and make it read only optional:
interface Duck {
readonlyid? :number;
name: string;
}
let dog: Duck = {
name: "yellow"};/ / error:
// Cannot assign to 'id' because it is a read-only property.
dog.id = 12;
Copy the code
That didn’t work either, so we found:
The read-only constraint exists the first time an object is assigned, not the first time a read-only property is assigned
Type of function
There are two ways to declare functions in js: function declaration and function expression. Let’s first look at the type definition of function declaration, just need to take the input and output of function into account.
function sum(x: number, y: number) {
return x + y;
}
Copy the code
An error will be reported if the call has more or less than the required parameters:
function sum(x: number, y: number) {
return x + y;
}
// An argument for 'y' was not provided.
sum(1)
// Expected 2 arguments, but got 3.
sum(1.2.3)
Copy the code
Functional expression
We can do the same for function expressions:
let sum = function (x: number, y: number) :number {
return x + y;
};
Copy the code
This only defines the anonymous function to the right of the equals sign, while the variable sum to the left of the equals sign is derived by type inference. We can also manually type it:
let sum: (x: number, y: number) = > number = function (x: number, y: number) :number {
return x + y;
};
Copy the code
Note that the => is not the same as the => in ES6. The => in TS is used to represent the function definition, with the input type on the left and the output type on the right.
We can also define function expressions through interfaces
interface ISumFunc {
(x: number.y: number) :number;
}
let sum: ISumFunc = function (x, y) {
return x + y;
};
Copy the code
Optional parameters
We said that the number of arguments to a function must be passed by definition, so how to define optional arguments? Similar to the optional properties of the interface definition object, which we use, right? To represent:
function buildName(firstName: string, lastName? :string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
buildName("Lucy")
buildName("Lucy"."Jessica")
Copy the code
Note that optional arguments must come after required arguments:
/ / error:
// A required parameter cannot follow an optional parameter.
function buildName(firstName? :string, lastName: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
returnfirstName; }}Copy the code
Parameter Default Value
As with es6 functions, we can also add default values to function arguments in TS:
function buildName(firstName: string = "Tom", lastName: string = "Cat") {
if (lastName) {
return firstName + "" + lastName;
} else {
returnfirstName; }}Copy the code
Ts automatically identifies parameters with default values as optional, so that optional parameters are not restricted after mandatory parameters:
function buildName(firstName? :string, lastName: string = "Cat") {
if (lastName) {
return firstName + "" + lastName;
} else {
returnfirstName; }}Copy the code
The remaining parameters
In ES6, we can use… Get the rest of the argument from the function:
function getRest(first, ... rest:any[]) {
rest.map((el) = > {});
}
getRest(1.2.3.4.5);
Copy the code
The rest argument is essentially an array of type any[], which can only be used as the last argument to a function.
Function overloading
Function overloading is defined to provide multiple function types for the same function. We can define multiple types of function overloading:
function add(x: number, y: string) :void;
function add(x: string, y: number) :void;
function add(x: string | number, y: number | string) :void {}
Copy the code
It is important to note that the exact definition must be first, and the final function implementation needs to use the joint type or any type, including all possible input types, to concrete implementation; That is, the bottom method needs to be compatible with the top method.
In the above example, although we defined three overloaded add functions, the first two were function definitions and the last one was function implementation, so we essentially only defined it twice:
function add(x: number, y: string) :void;
function add(x: string, y: number) :void;
function add(x: string | number, y: number | string) :void {}
/ / by
add(1.'1')
/ / by
add('2'.2)
/ / error:
//The call would have succeeded against this implementation, but implementation signatures of overloads are not externally visible.
add('2'.'2')
Copy the code
The last function call failed because we did not define two strings.
For more front-end information, please pay attention to the public number [front-end reading].
If you think it’s good, check out my Nuggets page. Please visit Xie xiaofei’s blog for more articles