What is TypeScript
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.
In short, TypeScript is a superset of JavaScript with optional types that can be compiled to pure JavaScript; technically TypeScript is JavaScript with static types.
TypeScript Pros and Cons
Enhance code maintainability, especially in large projects.
Friendly error hints in the editor, and type-checking at the compile stage to find most errors- Support for the latest JavaScript features
The surrounding ecological prosperity, vue3 has been fully supported typescript
- Requires some learning costs
Compatibility with some plugin libraries is not perfect, e.g. using typescript in a vue2 project was not so smooth.
Increase the cost of upfront development, after all, you need to write more code (but easier to maintain later)
installation environment
Install typescript
First of all, we can create a new empty folder for learning ts, for example, I created a new folder under the folder helloworld.ts
npm install -g typescript
If you don’t remember whether you have typescript installed or not, you can use the following command to verify:
tsc -v
If the version appears, the installation has been successful
Version 4.6.3
Generate tsconfig.json configuration file
tsc --init
After executing the command, we can see that a tsconfig.json file has been generated, which contains some configuration information, so let’s leave it at that for now.
In our helloworld.ts
file, write whatever you want.
The console executes the tsc helloworld.ts
command, and a helloworld.js file with the same name is generated in the directory with the following code
The tsc command reveals that our typescript code was converted to familiar js code
Let’s move on.
node helloworld.js
You can see the output
Install ts-node
So through our above a pass operation, we know that running the tsc command can be compiled to generate a js file, but if each change we have to manually go to the compilation, and then through the node command to view the results is not too much trouble.
And ts-node is here to solve this problem.
npm i -g ts-node
With this plugin, we can run the .ts file directly
Let’s try it.
ts-node helloworld.ts
You can see that our printout has output
All of our subsequent examples can be verified with this command
Now we can move on to typescript.
TypeScript Basic Types
Boolean Type
const flag: boolean = true;
Number Type
const count: number = 10;
String Type
let name: string = "";
Enum Type
Enumeration type is used to define a collection of values, using enumeration we can define some constants with names. Using enumerations you can clearly express the intent or create a set of differentiated use cases. For example, Monday through Sunday, orientation up and down, left and right, etc.
- normal enumeration
The initial value defaults to 0, and the rest of the members will automatically grow in order, which can be thought of as array subscripts.
enum Color {
RED,
PINK,
BLUE,
}
const red: Color = Color.RED;
console.log(red); // 0
- Setting the initial value
enum Color {
RED = 2,
PINK,
BLUE,
}
const pink: Color = Color.PINK;
console.log(pink); // 3
- string enumeration
const pink: Color = Color.PINK;
console.log(pink);
- constant enumeration
The difference between a constant enumeration and a normal enumeration is that the entire enumeration is deleted at the compilation stage when the enumeration is modified with the const keyword. We can see how it looks after compilation.
const enum Color {
RED,
PINK,
BLUE,
}
const color: Color[] = [Color.RED, Color.PINK, Color.BLUE];
console.log(color); //[0, 1, 2]
var color = [0 /* RED */, 1 /* PINK */, 2 /* BLUE */];
Array type
There are two ways to define an array type.
const arr: number[] = [1,2,3];
const arr2: Array<number> = [1,2,3];
Tuple type
The above array type approach can only define arrays that are all of the same type internally. Arrays with different internal types can be defined using the tuple type
Tuple (Tuple) represents a known number and type of arrays, it can be understood that he is a special kind of arrays
const tuple: [number, string] = [1, "zhangmazi"];
Note that the tuple type can only represent an array with a known number and type of elements, the length is specified, and out-of-bounds access will prompt an error. For example, an array may have multiple types in it, the number and type of which are uncertain, then just any[].
undefined and null
By default null and undefined are subtypes of all types. That means you can assign null and undefined to other types.
let a: undefined = undefined;
let b: null = null;
let str: string = 'zhangmazi';
str = null;
str = undefined;
If you specify “strictNullChecks”:true in tsconfig.json, i.e., if strict mode is enabled, null and undefined can only be assigned to void and their respective types. (Thanks to the comments section for pointing this out.) null and undefined can only be assigned to their own types.
// 启用 --strictNullChecks
let x: number;
x = 1;
x = undefined;
x = null;
But undefined can assign a value to a void
let c:void = undefined
let d:void = null
any Type
any skips the type checker’s checking of the value, and any value can be assigned to the any type
let value: any = 1;
value = "zhangmazi";
value = [];
value = {};
void Type
void means invalid, usually only used in functions, tell others that the function has no return value.
function sayHello(): void {
console.log("hello");
}
never Type
The never type represents the type of values that never exist. For example, the never type is the type of the return value of a function expression or arrow function expression that always throws an exception or never has a return value at all
Two cases where the value will never exist:
1 If a function throws an exception while executing, then the function never has a return value (because throwing an exception directly interrupts the program, which makes the program go no further than the return value, i.e., it has an unreachable endpoint, and there is never a return).
2 Code that executes an infinite loop in a function (a dead loop) so that the program never runs to the point where the function returns a value, and there is never a return.
function error(msg: string): never {
throw new Error(msg);
}
function loopForever(): never {
while (true) {};
}
Unknown Type
UNKNOWN is the same as ANY and all types can be assigned to UNKNOWN:.
let value: unknown = 1;
value = "zhangmazi";
value = false;
The big difference between UNKNOWN and ANY is:
A value of any type can be assigned to any, and a value of type any can be assigned to any. unknown A value of any type can be assigned to it, but it can only be assigned to unknown and any.
object type
object, Object and {} types
The object object type is used to represent all non-primitive types, i.e., we can’t assign primitive types such as number, string, boolean, symbol, etc., to an object. null and undefined types can’t be assigned to an object in strict mode either.
let object: object;
object = 1;
object = "a";
object = true;
object = null;
object = undefined;
object = {};
- Object
Big Object represents all types that have toString, hasOwnProperty methods, so all primitive and non-primitive types can be assigned to Object (null and undefined are not allowed in strict mode).
let bigObject: Object;
object = 1;
object = "a";
object = true;
object = null;
ObjectCase = undefined;
ObjectCase = {}; // ok
- {}
{} A null object type, like a large Object, represents a collection of primitive and non-primitive types.
In TypeScript, we define a class using the Class keyword.
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHi(): void {
console.log(`Hi, ${this.name}`);
}
}
const flag1: number[] = [1, 2, 3];
const flag2: Array<number> = [1, 2, 3];
function declaration
function add(x: number, y: number): number {
return x + y;
}
function expression (math.)
const add = function(x: number, y: number): number {
return x + y;
}
Interface Definition Functions
interface Add {
(x: number, y: number): number;
}
Optional parameters
function add(x: number, y?: number): number {
return y ? x + y : x;
}
default parameter
function add(x: number, y: number = 0): number {
return x + y;
}
Remaining parameters
function add(...numbers: number[]): number {
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
return sum;
}
function overloading
Function overloading or method overloading is an ability to create multiple methods using the same name and different number or types of arguments.
function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any): any {
return x + y;
}
In the above example, we provide multiple function type definitions for the same function, thus enabling function overloading
Points to note.
Function overloading is really implemented in the function body of the last function of the same name defined before the last function body definition are function type definitions can not write specific function implementation methods can only define the type of
genre inference
If a type is not explicitly specified, then TypeScript will infer a type according to the rules of type inference.
let x = 1;
x = true;
The code above is equivalent to
let x: number = 1;
x = true;
As you can see from the above example, when we don’t specify an explicit type for x, typescript will infer that x is of type number.
And if it is defined without assignment, it will be inferred to be of type any without being type-checked at all, regardless of whether it is assigned afterwards or not:
let x;
x = 1;
x = true;
type assertion
In some cases, we may know the type of a variable better than typescript, so we may want to manually specify the type of a value
Type assertions are made in two ways
- angle brackets < >
let str: any = "to be or not to be";
let strLength: number = (<string>str).length;
- as Writing
let str: any = "to be or not to be";
let strLength: number = (str as string).length;
nonempty assertion
In context when the type checker is unable to assert a type, the affix expression operator !
can be used to assert that the object of the operation is of a non-null and non-undefined type, i.e. the value of x! will not be null or undefined
let user: string | null | undefined;
console.log(user!.toUpperCase());
console.log(user.toUpperCase());
Determining Assignment Assertions
let value:number
console.log(value); // Variable 'value' is used before being assigned.
If we define a variable and use it without assigning a value to it, we get an error
By letting x!: number; determine the assignment assertion, the TypeScript compiler knows that the property will be explicitly assigned.
let value!:number
console.log(value);
Type of association
Union types are separated by |
, indicating that the value can be one of several types
let status:string|number
status='to be or not to be'
status=1
type alias
A type alias is used to give a new name to a type. It just gives a new name, it does not create a new type. Type aliases are often used for union types.
type count = number | number[];
function hello(value: count) {}
Cross-cutting type
A cross type is the opposite of a union type and is represented by the &
operator; a cross type is when two types must be present
interface IpersonA{
name: string,
age: number
}
interface IpersonB {
name: string,
gender: string
}
let person: IpersonA & IpersonB = {
name: "",
age: 18,
gender: ""
};
person is the type of IpersonA and IpersonB.
Note: Cross-typing takes the concatenation of multiple types, but if the key is the same but of a different type, then the key is of type never
interface IpersonA {
name: string
}
interface IpersonB {
name: number
}
function testAndFn(params: IpersonA & IpersonB) {
console.log(params)
}
testAndFn({name: ""}) // error TS2322: Type 'string' is not assignable to type 'never'.
Type Guard
Type protection is an expression for executable runtime checks to ensure that the type is within certain limits. In other words, type protection ensures that a string is a string, even though its value can also be a numeric value. Type protection is not entirely different from feature detection; the main idea is to try to detect properties, methods, or prototypes to determine how to handle values.
In other words: type guards are runtime checks to make sure that a value is within the range of the desired type
There are currently four main ways to implement type protection:
- 1. in keyword
interface InObj1 {
a: number,
x: string
}
interface InObj2 {
a: number,
y: string
}
function isIn(arg: InObj1 | InObj2) {
if ('x' in arg) console.log('x')
if ('y' in arg) console.log('y')
}
isIn({a:1, x:'xxx'});
isIn({a:1, y:'yyy'});
- 2、typeof keyword
function isTypeof( val: string | number) {
if (typeof val === "number") return 'number'
if (typeof val === "string") return 'string'
return ''
}
function creatDate(date: Date | string){
console.log(date)
if(date instanceof Date){
date.getDate()
}else {
return new Date(date)
}
}
- 4. Type predicates for custom type protection
function isNumber(num: any): num is number {
return typeof num === 'number';
}
function isString(str: any): str is string{
return typeof str=== 'string';
}
We use interfaces to define the types of objects. An interface is an abstraction (description) of an object’s state (properties) and behavior (methods)
The simple understanding is: provide a convention for our code
We use the keyword interface to declare an interface
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
We define an interface Person, and then we define a variable tom, whose type is Person, so that we constrain the shape of tom to be consistent with the interface Person.
Interfaces are generally capitalized. (Of course, a lot of people also use the I capitalization to indicate that it’s an interface.)
Setup Interface Optional | Read Only
interface Person {
readonly name: string;
age?: number;
}
The optional attribute, our most common use case, is that we are not sure if this parameter will be passed, or exists.
The read-only attribute is used to restrict the modification of an object’s value to only when it has just been created. In addition, TypeScript provides the ReadonlyArray type, which is similar to Array, except that it removes all mutable methods, thus ensuring that an array can never be modified after it is created.
indexed signature
Sometimes we want an interface to allow arbitrary attributes in addition to mandatory and optional attributes, and we can use indexed signatures to fulfill this requirement.
Note that once an arbitrary property is defined, the types of both the deterministic and optional properties must be a subset of its type
interface Person {
name: string;
age?: number;
[prop: string]: any;
}
const p1:Person = { name: "" };
const p2:Person = { name: "", age: 28 };
const p3:Person = { name: "", sex: 1 }
We provide for indexing by a value of type string, which indexes to a value of type any.
Differences between interfaces and type aliases
In fact, in most cases, using interface types and type aliases is equivalent, but in some specific scenarios there is a big difference between the two.
One of the core principles of TypeScript is type checking the structures that values have. And the role of the interface is to name these types and define the data model for your code or third-party code.
type (type alias) gives a new name to a type. type is sometimes similar to interface, but works on primitive values (basic types), union types, tuples, and anything else you need to write by hand. Making an alias doesn’t create a new type – it creates a new name to refer to that type. Aliasing basic types is usually not useful, although it can be used as a form of documentation.
Both interfaces and type aliases can be used to describe the type of an object or function, just with a different syntax
type MyTYpe = {
name: string;
say(): void;
}
interface MyInterface {
name: string;
say(): void;
}
Both allow for expansion
interface is extended withextends
interface MyInterface {
name: string;
say(): void;
}
interface MyInterface2 extends MyInterface {
sex: string;
}
let person:MyInterface2 = {
name:'',
sex:'',
say(): void {
console.log("hello!");
}
}
- type Use
&
to implement extensions
type MyType = {
name:string;
say(): void;
}
type MyType2 = MyType & {
sex:string;
}
let value: MyType2 = {
name:'',
sex:'',
say(): void {
console.log("hello!");
}
}
type can declare basic datatype aliases/joint types/tuples, etc., whereas interface cannot.
type UserName = string;
type UserName = string | number;
type Animal = Pig | Dog | Cat;
type List = [string, boolean, number];
- interface can merge declarations, but type cannot.
interface Person {
name: string
}
interface Person {
age: number
}
A generalization is a feature that does not prespecify a specific type when defining a function, interface, or class, but rather specifies the type when it is used.
For example, let’s say we have a requirement now that we want to implement a function where the arguments to the function can be any value, and the return value is to return the arguments as they are, and the type of the arguments is string, and the function return type is string?
It’s easy for you to write down:
function getValue(arg:string):string {
return arg;
}
Now that the requirements have changed and you need to return a value of type number, you’d say that the union type would do the trick:
function getValue(arg:string | number):string | number {
return arg;
}
But then there’s the problem that if we need to return a boolean, an array of strings or even an arbitrary type, do we write as many union types as there are?
Yes, we’ll just use any!
function getValue(arg:any):any {
return arg;
}
Although any is good, and often any does solve a lot of problems, it doesn’t meet our needs, and the pass and return are both of type any, which doesn’t unify the pass and return.
As a programmer with a tawdry most demanding program, can we have any other solutions?
It’s time to bring out our generalizations!
Basic use
A generalization is a feature that does not prespecify a specific type when defining a function, interface, or class, but rather specifies the type at the time of use
The above requirement, if we use generalization to solve it:
function getValue<T>(arg:T):T {
return arg;
}
The syntax of a generic is to write the type parameter inside the angle brackets <>
, usually T
is used to indicate the first type variable name, in fact, it can be replaced by any valid name, for example, if we use NIUBI
, it will also compile correctly.
A generic type is like a placeholder for a variable, and when using it we can pass in the defined type like a parameter, and output it as-is
We have two ways to use it:
- Define the type to be used, for example:
getValue<string>('');
- Utilize typescript’s type inference, for example:
getValue('')
Multiple parameters
There is not really only one type variable that can be defined; we can introduce as many type variables as we wish to define. For example, we introduce a new type variable U
function getValue<T, U>(arg:[T,U]):[T,U] {
return arg;
}
const str = getValue(['', 18]);
typescript gives us the ability to automatically infer the type of input and return.
generalized constraint
When using a generic variable inside a function, you can’t manipulate its properties or methods at will because you don’t know which type it is beforehand:
function getLength<T>(arg:T):T {
console.log(arg.length);
}
Since the generic T does not necessarily contain the attribute length, I want the getLength function to only allow passing in variables that contain the length attribute, how do I do that?
At this point, we can use the extends
keyword to constrain the generalization
interface Lengthwise {
length: number;
}
function getLength<T extends Lengthwise>(arg:T):T {
console.log(arg.length);
return arg;
}
const str = getLength('')
const arr = getLength([1,2,3])
const obj = getLength({ length: 5 })
As you can see here, it doesn’t matter if you’re a str, arr, or obj, as long as you have the length attribute, you can
For more information, see Easy TS Floods.
generic interface
Specify the generic type when defining the interface
interface KeyValue<T,U> {
key: T;
value: U;
}
const person1:KeyValue<string,number> = {
key: '',
value: 18
}
const person2:KeyValue<number,string> = {
key: 20,
value: ''
}
泛型类
class Test<T> {
value: T;
add: (x: T, y: T) => T;
}
let myTest = new Test<number>();
myTest.value = 0;
myTest.add = function (x, y) {
return x + y;
};
Generic type aliases
type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = { list: ["1"] };
let c2: Cart<number> = [1];
Default types for generic parameters
We can specify a default type for the type parameter in a generic. This default type works when the type parameter is not specified directly in the code when using a generic, and cannot be inferred from the actual value parameter. It’s kind of like the default parameter of a function in js.
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
Generic Tool Types
- typeof
Keywords can push out types from implementations, in addition to doing type protection.
let p1 = {
name: "",
age: 18,
gender: "male",
};
type People = typeof p1;
function getName(p: People): string {
return p.name;
}
getName(p1);
- keyof
can be used to get all the key values in an object’s interface
interface Person {
name: string;
age: number;
gender: "male" | "female";
}
type PersonKey = keyof Person; //type PersonKey = 'name'|'age'|'gender';
function getValueByKey(p: Person, key: PersonKey) {
return p[key];
}
let val = getValueByKey({ name: "", age: 18, gender: "male" }, "name");
console.log(val);
- in
Used to iterate through enumerated types:
type Keys = "a" | "b" | "c"
type Obj = {
[p in Keys]: any
} // -> { a: any, b: any, c: any }
- infer
In a conditional type statement, you can declare a type variable with infer and use it.
type ReturnType<T> = T extends (
...args: any[]
) => infer R ? R : any;
infer R is to declare a variable to hold the type of the return value passed into the function signature, in short, it is used to get the type of the function’s return value for later use.
- extends
Sometimes we define generalizations that we don’t want to be too flexible or that we want to inherit from certain classes, etc. You can add generalization constraints with the extends keyword.
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
This generalized function is now defined with constraints, so it no longer applies to arbitrary types:
loggingIdentity(3); // Error, number doesn't have a .length property
When we pass in a value of a legal type, i.e. a value that contains the length attribute:
loggingIdentity({length: 10, name: ''});
- index access operator
Index access is possible using the []
operator:
interface Person {
name: string;
age: number;
}
type x = Person["name"]; // x is string
Type of built-in tools
- Required
Make attributes of types mandatory
interface Person {
name?: string,
age?: number,
hobby?: string[]
}
const user: Required<Person> = {
name: "",
age: 18,
hobby: ["code"]
}
- Partial
Converts all properties to optional, as opposed to Required.
interface Person {
name: string,
age: number,
}
const shuge:Person = {
name:''
} // error Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.
From the above, we know that if we have to pass it and we have to pass it less, we will get an error.
We use Partial to make it optional.
type User = Partial<Person>
const shuge: User={
name:''
}
- Exclude
Exclude<T, U>
The function of this function is to remove the type of a type that belongs to another type, and the remaining attributes form a new type.
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
- Extract
In contrast to Exclude, Extract<T,U>
extracts U from T.
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () =>void
Applies to: Concatenation type
- Readonly
Converts all property values of an array or object to read-only, which means that these properties cannot be reassigned.
interface Person {
name: string;
age: number;
gender?: "male" | "female";
}
let p: Readonly<Person> = {
name: "hello",
age: 10,
gender: "male",
};
p.age = 11; // error Cannot assign to 'age' because it is a read-only property.
- Record
The role of Record <K extends keyof any, T> is to convert the values of all attributes in K to T types.
type Property = 'key1'|'key2'
type Person = Record<Property, string>;
const p: Person = {
key1: "hello",
key2: "",
};
- Pick
Pick out some attributes from a certain type
type Person = {
name: string;
age:number;
gender:string
}
type P1 = Pick<Person, "name" | "age">; // { name: string; age: number; }
const user:P1={
name:'',
age:18
}
- Omit
In contrast to Pick, Omit<T,K>
removes all other attributes from T excluding K.
interface Person {
name: string,
age: number,
gender: string
}
type P1 = Omit<Person, "age" | "gender">
const user:P1 = {
name: ''
}
- NonNullable
Remove null
from the type and undefined
type P1 = NonNullable<string | number | undefined>; // string | number
type P2 = NonNullable<string[] | null | undefined>; // string[]
- ReturnType
Used to get the return value type of a function
type Func = (value: string) => string;
const test: ReturnType<Func> = "1";
- Parameters
The type of the tuple used to obtain the types of the tuples comprising the function’s argument types.
type P1 = Parameters<(a: number, b: string) => void>; // [number, string]
- InstanceType
Returns the type of instance of constructor type T
class C {
x = 0;
y = 0;
}
type D = InstanceType<typeof C>; // C
tsconfig.json
In the environment installation section at the beginning of the article, remember that we have generated a tsconfig.json file, so what exactly is the use of this file?
tsconfig.json is the configuration file for the TypeScript project.
tsconfig.json contains the configuration for TypeScript compilation. By changing the compilation configuration, we can make TypeScript compile ES6, ES5, and node code.
important field
- files – Sets the name of the files to be compiled;
include – Sets the files to be compiled, supports path pattern matching;
exclude – Sets files that do not need to be compiled, supports path pattern matching;
compilerOptions – Sets options related to the compilation process.