preface
In 2020, more and more open source front-end projects have or are using TypeScript refactoring (Ant Design, VS Code, VUe3, etc.). We can’t ignore the impact of TypeScript’s entire ecosystem on front-end development.
Compile time Error vs Runtime Error
As we can see from the picture, compilation errors can be remedied, but when the bug is online, it can cause a crash to the system page, which can be costly.
The chart below is from a 2019 Developer survey by StackOverflow. As you can see, TypeScript is already tied with Python for second place on the list of most popular languages for developers.stackoverflow Developer Survey Results 2019
Furthermore, as you can see from the picture below, most bugs in front-end projects are type driven.
One called “To Type or Not To Type: The Quantifying Bugs in JavaScript study found that 15% of Bugs in Github repositories with TypeScript were avoided.
Another survey showed that 62% of NPM package developers were using TS in 2018.
So TypeScript is unstoppable.
Problems with JavaScript
So what leads to TS? At its root, JavaScript is a dynamically weakly typed language that is tolerant of variable types and prone to low-level errors.
For example, we often make hand errors (typing one more character per word) : the IDE automatically flags the following code in TypeScript red to avoid bringing bugs to the online environment.
const regions = [
{
label: Sichuan Province,
value: 1
},
{
label: 'Luzhou',
value: 20
},
{
label: 'Longmattan District', value: 3310 } ]; const region = regions.map((ele) => ele.values); // Attribute "values" in type "{label: string; value: number; } "does not exist on. Do you mean "value"? ts(2551)Copy the code
The difference between weak typing and strong typing in a programming language
- Strongly typed: a language that enforces data type definition, a type-safe language (if an integer variable a is defined, it cannot be treated as a string without a display conversion)
- Weak type: there are no constraints and relatively flexible (a variable can be assigned values of different data types)
Programming languages The difference between dynamic and static languages
- Static languages: Determine the types of all variables at compile time
- Dynamic languages: Determine the types of all variables at execution time
What is a TypeScript?
TypeScript is a superset of JavaScript types that can be compiled into pure JavaScript. The compiled JavaScript can run on any browser. TypeScript compilation tools can run on any server and on any system. TypeScript is open source.
TypeScript is more of a tool than a language.
What benefits does TypeScript bring?
- Type safety: Strict type checking is performed when code is compiled. Most errors can be found at compile time, which is better than going live.
- Next generation JS features
- Complete tool chain
- Increase productivity
- Increased code readability and maintainability: code and documentation
- With VS Code and other editors: code hints, automatic completion, automatic import and other functions
- Strong community support
How do I know if I need TypeScript?
- What is the scale of the project
- Whether there is multi-party cooperation in the project
- Whether there will be new personnel
- Whether the project needs long-term maintenance
Quick introduction: Static typing basics
TypeScript underlying data types
The ES6 has the following data types: Boolean, Number, String, Array, Function, Object, Symbol, undefined, and NULL.
TypeScript builds on ES6 with the following data types:
- Void: indicates a type that does not return any value
- Never: a type that never returns a value (application: functions throw exceptions, dead-loop functions)
- Tuple: The tuple type is used to represent an array with the known number and type of elements. The types of the elements need not be the same.
- The enumeration
- Advanced types: condition type/map type/index type, etc
- any
- unknown
// Primitive typelet bool: boolean = true
let bool2 = true
let str: string = 'echo'// STR = 123 // arraylet arr1: number[] = [1, 2, 3, 4]
let arr2: Array<number | string> = [1, 2, 3, '4'] / / tupleslet tuple: [number, string] = [0, '1'] tupl.push (2)console.log(tuple)// tuple[2] // functionlet add = (x: number, y: number) => x + y
letCompute: (x: number, y: number) => numberCompute = (a, b) => a + b // objectlet obj: { x: number; y: number } = { x: 1, y: 2 }
obj.x = 3
// symbol
let s1: symbol = Symbol()
let s2 = Symbol()
console.log(s1 === s2)
// undefined, null
let loading: undefined = undefined
let nu: null = null
// loading = 123
// void
let noReturn = () => {}
// any
let xx = 1
x = []Copy the code
One concept: set theory
In TypeScript, “type” is understood as a collection of values. The type string is a collection of strings.
Any and unknown are supertypes of any type (that is, the top type). Any value can be annotated as any or unknown.
Never is a subset of all other non-empty types. So never is the bottom type.
TypeScript by operator union (|) and intersection computes (&) to identify the type at the top and bottom type, such as the following:
T | never = > T / / never take and sets the T and T, it can be seen that never is an empty collection, any values are not annotated as never. T & unknown => T // The intersection of unknown and T is T, so other types are subsets of unknown types.Copy the code
never
// never
let error = () => {
throw new Error('error')}let endless = () => {
while(true) {}}Copy the code
Another application is to use the never: NonNullable type in conditional types, for example, to exclude null and undefined from T. Its definition is as follows:
type NonNullable<T> = T extends null | undefined ? never : T
type T0 = NonNullable<string | number | undefined>; // string | number
type T1 = NonNullable<string[] | null | undefined>; // string[]Copy the code
The difference between unknown and any
Unknown is more restrictive. Most operations on unknown types (instantiation, getters, function execution) must be narrowed down.
let value: any;
value.foo.bar; // OK
value.trim(); // OK
value(); // OK
new value(); // OK
value[0][1]; // OK
let value2: unknown;
value2.foo.bar; // Error
value2.trim(); // Error
value2(); // Error
new value2(); // Error
value2[0][1]; // ErrorCopy the code
Unknown Reduces the type to a specific type to implement the type protection function of TS.
/**
* A custom type guard function that determines whether
* `value` is an array that only contains numbers.
*/
function isNumberArray(value: unknown): value is number[] {
return Array.isArray(value) && value.every((element) => typeof element === 'number');
}
const unknownValue: unknown[] = [ 15, 23, 8, 4, 42, 16 ];
const unknownValue2: unknown[] = [ '1', 23, 8, 4, 42, 16];if (isNumberArray(unknownValue)) {
// Within this branch, `unknownValue` has type`number[]`, // so we can spread the numbers as arguments to `Math.max` const max = Math.max(... unknownValue); console.log(max); }Copy the code
Type annotations and type inference
Type annotations
Equivalent to type declarations in strongly typed languages
Syntax: (variable/function) : type
// Primitive type const user: string ="Echo Zhou"
const haha: boolean = false/ / functionfunction warnUser(): void {
alert("This is my warning message"); } / / arrayletArr1: number[] = [1, 2, 3] // tuplelet tuple: [number, string] = [0, '1']
Copy the code
Type inference
What is type inference:
Instead of specifying the type of a variable (the return value type of a function), TypeScript can automatically infer a type for it based on certain rules.
Types of assertions
Type assertion refers to the fact that a type can be manually specified, allowing variables to change from one type to another
Cross type versus union type
To cross type is to combine multiple types into one type.
Union type: The declared type is indeterminate and can be one of multiple types.
interface DogInterface {
run(): void
}
interface CatInterface {
jump(): void
}
let pet: DogInterface & CatInterface = {
run() {},
jump() {}}let a: number | string = 1
let b: 'a' | 'b' | 'c'
let c: 1 | 2 | 3
class Dog implements DogInterface {
run() {}
eat() {}
}
class Cat implements CatInterface {
jump() {}
eat() {}
}
enum Master { Boy, Girl }
function getPet(master: Master) {
let pet = master === Master.Boy ? new Dog() : new Cat();
// pet.run()
// pet.jump()
pet.eat()
return pet
}
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Rectangle | Circle
function area(s: Shape) {
switch (s.kind) {
case "square":
return s.size * s.size;
case "rectangle":
return s.height * s.width;
case 'circle':
return Math.PI * s.radius ** 2
default:
return ((e: never) => {throw new Error(e)})(s)
}
}
console.log(area({kind: 'circle', radius: 1}))Copy the code
The TypeScript core
Duck identification
What is duck identification? An object is said to be a duck if it walks like a duck and quacks like a duck.
TypeScript is all about type checking the structure of a value, so TS can also be called duck typing.
interface
A contract (shape) defined to constrain the structure of an object, function, or class
interface IWarningModProps {
form: FormProps['form'];
visible: boolean;
handleModalVisible: (flag: boolean) => void;
closeModal: () => void;
threshold: FetchCreThresholdSuccData['info']
customerId: number;
}Copy the code
class
Member modifiers and abstract classes and methods
Private: can only be called by the class itself, not by instances or subclasses of the class. The constructor adds private to indicate that the class cannot be instantiated or inherited.
Protected: Only called in classes and subclasses, not in instances
Static (static member modifier of a class) : can only be called by the class name
Abstract classes and abstract methods: Used to implement polymorphism and extract commonalities (abstract methods are defined in parent classes and can have different implementations in subclasses).
abstract class A {
public A1: string = 'TS';
private A2: number = 123;
protected A3: string = 'GOOD';
static A4: number = 123;
public FA1() {
console.log('123');
}
private FA2() {}
protected FA3() {}
static FA4() {// Abstract methods must be implemented by subclasses console.log()'Dog sleep');
}
abstract FA5(): void;
}
class B extends A {
public B1: string = '123';
private B2: number = 123;
protected B3: string = '1234';
static B4: number = 1234;
F5() {}
private FB5() {}
protected FB6() {}
static FB7() {
console.log('Dog sleep');
}
public FA5() {}} // instantiate Blet b = new B();Copy the code
Classes and interfaces
- Interfaces represent primarily abstract behavior. Properties and methods cannot be initialized. When a class implements an Interface, it can externalize the behavior.
- When a class implements an interface, it must implement all properties and methods in the interface.
- An interface can inherit from multiple interfaces, using the extend keyword, separated by commas.
- A class can only inherit from another class, and Mixins are needed to implement a class that can inherit from multiple classes.
// Implements implements interface to improve object-oriented flexibility interface Alarm {alert(): void; } interface Light { lightOn(): void; lightOff(): void; } class Car implements Alarm, Light {alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off'); }}Copy the code
Interface and type keywords
- First of all, type alias is more extensive than interface. In addition to defining object types, it can also define cross, union, and primitive types.
- When we need to merge two types, the cross type of type is more readable, although the extends of Interface can also implement a declarative merge. In this case, type is recommended.
- But interfaces can implement extends and implements of interfaces. Interface can implement merge declaration of interfaces. Type can be implemented using cross types instead.
In general, when we need to develop a common library, it is best to use interface, interface inheritance and implementation features to facilitate user extension.
interface MilkshakeProps { name: string; price: number; } interface MilkshakeMethods { getIngredients(): string[]; } interface Milkshake extends MilkshakePropstypeCross type oftype Milkshake = MilkshakeProps & MilkshakeMethods;Copy the code
The generic
Generics are everywhere, whether in third-party library declarations or in our own practice projects, to keep code readable and concise without losing the flexibility of our programs.
(From an article reading Typescript generics and applications :juejin.cn/post/684490…)
There are two key points to understand generics:
- No pre-determined data type is required; the specific type is determined at the time of use.
- Generics are like parameters in functions (we can think of them as type parameters). When defining a function, type, interface, or class, the name is followed by <> to indicate that the value can also be passed as an argument.
Practice 1: React class components
In the React declaration file, all of its apis are redefined.
For example, Component is defined as a generic class that takes three arguments
- P: The default object is empty and is of the property type
- S: The default object is empty and is of state type
- SS: Never mind.
interface Component<P = {}, S = {}, SS = any> extends ComponentLifecycle<P, S, SS> { }Copy the code
interface DirProps { // ... } class Dir extends Component<DirProps, DirState> {}Copy the code
Practice 2: Generics make it easy to use apis
In a project, we usually extend or re-wrap AXIos. For example, in a real project with a request.js file, we define an AXIos utility class that provides public methods like GET, POST, and PUT, and then instantiate and export them for use.
Look at the following examples:
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import { RemoveStorage } from '@/utils/utils';
import { GetStorage } from '@/utils/utils';
import { message } from 'antd';
const axiosInstance = axios.create(axiosConfig)
exportinterface IHttpClient { getStoreConfig: () => { baseURL: string; headers: { webToken: string; }; }; get: <T>(url: string, config? : AxiosRequestConfig) => Promise<T>; post: <T>(url: string, data? : any, config? : AxiosRequestConfig) => Promise<T>; }... // Omit here, for example, add generic configuration, interceptor, etc. const _http = axiosInstance; class Axios implements IHttpClient { publicgetStoreConfig() {
return {
baseURL: '/xxx/api',
headers: { webToken: GetStorage('token')}}; } public async get<T>(url: string, config? : AxiosRequestConfig): Promise<T> { const configInfo = config ? config : this.getStoreConfig(); const response: AxiosResponse = await _http.get(url, configInfo);returnresponse; } public async post<T>(url: string, data? : any, config? : AxiosRequestConfig): Promise<T> { const configInfo = config ? config : this.getStoreConfig(); const response: AxiosResponse = await _http.post(url, data, configInfo);returnresponse; }}export const fetch: IHttpClient = new Axios();
Copy the code
You must have a question 🤔️?
get(url: string, config? : AxiosRequestConfig ) => Promise<T> ?
What to make of the generic T in Promise<T>?
- You can think of it as the value of resolve after a Promise becomes a success state, the type of argument passed to resolve.
Let’s look at this example:
When you pass no generics, TS deduces that the return type of a Promise (after it becomes a success state) is unknown.
With the following changes, TypeScript can deduce that Promise returns a value of type number.
So when we write apis, we can pass in the generic T type, and TypeScript can then deduce the return value type of the interface.
Here is the TS type of the Promise constructor in the TS declaration file.
/// <reference no-default-lib="true"/>
interface PromiseConstructor {
/**
* A reference to the prototype.
*/
readonly prototype: Promise<any>;
/**
* Creates a new Promise.
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
* a resolve callback used to resolve the promise with a value or the result of another promise,
* and a reject callback used to reject the promise with a provided reason or error.
*/
new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
...
/**
* Creates a new rejected promise forthe provided reason. * @param reason The reason the promise was rejected. * @returns A new rejected Promise. */ reject<T = never>(reason? : any): Promise<T>; /** * Creates a new resolved promiseforthe provided value. * @param value A promise. * @returns A promise whose internal state matches the provided promise. */ resolve<T>(value: T | PromiseLike<T>): Promise<T>; /** * Creates a new resolved promise . * @returns A resolved promise. */ resolve(): Promise<void>; }declare var Promise: PromiseConstructor;
Copy the code
Advanced features
The index type
Typeof: typeof obtaining JS values
The typeof operator can be used to get a variable declaration or the typeof an object.
Keyof T: Key to get the type
Index query operator that can be used to get all keys of a type whose return type is the union type.
So these two operators are often used together:
const defaultColConfig = {
xs: 24,
sm: 24,
md: 12,
lg: 12,
xl: 8,
xxl: 6,
};
type colConfig = keyof typeof defaultColConfig
// type colConfig = "xs" | "sm" | "md" | "lg" | "xl" | "xxl"Copy the code
T[K] : Gets the value of the type
Application scenario: Select some attribute values from an object
interface Iobj1 {
a: number;
}
type typea = Iobj1['a']; // string
let obj1 = {
a: 1,
b: 2,
c: 3
};
function getValues<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
return keys.map((key) => obj[key]);
}
const a1 = getValues(obj1, [ 'a'.'b']); / / [1, 2]Copy the code
Other related applications
Keyof is used in TypeScript’s built-in type Omit, Record, and more.
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
type Record<K extends keyof any, T> = { [P in K]: T };
type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>; / *typeThreeStringProps = { prop1: string; prop2: string; prop3: string; } * /Copy the code
Mapping type
Definition: Create a new type from an old type
// Partial: Makes all attributes of a map optionaltype Partial<T> = { [P inkeyof T]? : T[P] } interface Obj { a: string; b: number; }typePartialObj = Partial<Obj> // Pick: Extract the desired attribute from the maptype PickObj = Pick<Obj, 'a' | 'b'>
interface Customer {
id: number;
name: string;
address: string;
region: string;
parent: number;
}
type KeyProps = Pick<Customer, 'id' | 'name'>; // Record: construct has type K astypeProperty of type T, which can be used to map properties of one type to another type.type Record<K extends keyof any, T> = { [P in K]: T };
type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>; / *typeThreeStringProps = { prop1: string; prop2: string; prop3: string; } * /type RecordObj = Record<'x' | 'y', Obj>
Copy the code
Condition types: infer and extends keywords
The extends keyword
In conditional statements, extends does not mean inheritance. For example, the condition T extends U? The extends in X: Y should be understood as follows: if type T can be assigned to type U, then the resulting type is X, otherwise Y.
Infer the keyword
Infer: Means that in the extends condition statement, TypeScript does type inference and stores the inference results in variables following the infer keyword.
type ReturnType<T> = T extends (… args: any[]) => infer P ? P : any;
- In the above conditional statement, P represents the type of function return value to be inferred.
Actual application scenarios:
typeReturnType<T> = T extends (... args: any[]) => infer P ? P : any;function transCus () {
return {
name: 'echo',
region: [Sichuan Province.'Luzhou'.'Longmattan District'],
address: 'xxx',
metaType: 3,
type: 3}}type Customer = ReturnType<typeof transCus>
/*
type Customer = {
name: string;
region: string[];
address: string;
metaType: number;
type: number; } * /Copy the code
There are many other keywords for conditional types that are not explained in detail.
- Exclude
,>
— Exclude from T any type that can be assigned to U.
- Extract
,>
— Extract a type from T that can be assigned to U.
- NonNullable
— Remove null and undefined from T.
- InstanceType
— gets the InstanceType of the constructor type.
Engineering related
Declaration file
Usually with a d.ts suffix, we call it a declaration file.
When using a third-party library, we need to reference its declaration file to get the corresponding code completion, interface prompts and other functions.
When we use a non-TS class library (jquery), we have to write a declaration file for the library that exposes its API.
Sometimes, some library declarations are written in the source code (such as ANTD), and sometimes they are provided separately and require additional installation (such as jquery).
Most of the class library declaration files have been written for us by the community. We just need to install it.
npm i @types/jquery -DCopy the code
Contribution to the community: definitelytyped.org/guides/cont…
Conclusion: When the library itself does not have its own declaration file, we need to download and install it from DefinitelTyped. If DefinitelTyped does not have its own third-party component or library, then we need to declare Module.
When we need to customize methods for other libraries
// Module plugin import m from'moment';
declare module 'moment' {
export function myFunction(): void;
}
m.myFunction = () => {}Copy the code
Read more: ts.xcatliu.com/basics/decl…
Configuration file tsconfig.json
Files: Represents a list of individual files that the compiler needs to compile.
Include: [] Supports wildcards, files or directories that the compiler needs to compile.
Compilation options:
- Target: What version of the target language are we compiling into
‘ES3’ (default), ‘ES5’, ‘ES2015’, ‘ES2016’, ‘ES2017’, ‘ES2018’, ‘ES2019’ or ‘ESNEXT’
- Module: What module system will we compile our code into
‘none’, ‘commonjs’, ‘amd’, ‘system’, ‘umd’, ‘es2015’, or ‘ESNext’.
{
"files": [
"src/a.ts"]."include": [
"src/*"// only compile SRC files],"compilerOptions": {
// "incremental": true// Incremental compilation //"tsBuildInfoFile": "./buildFile"// Where the incremental compiled file is stored //"diagnostics": true, // Print diagnostic information //"target": "es5"// Target language version //"module": "commonjs"// Generate code module standard //"outFile": "./app.js"// Generate a file from multiple interdependent files that can be used in AMD modules //"lib": [], // The library that TS needs to reference, i.e. the declaration file, es5 default"dom"."es5"."scripthost"
// "allowJs": true// Allows you to compile JS files (JS, JSX) //"checkJs": true, // allows error reporting in JS files, usually with allowJS //"outDir": "./out"// Specify the output directory //"rootDir": ". /"// Specify the input file directory (for output) //"declaration": true// Will automatically generate declaration files for us //"declarationDir": "./d"// Declare the path of the file //"emitDeclarationOnly": true// Only declaration files are generated //"sourceMap": true// Generate the object filesourceMap
// "inlineSourceMap": true, // Generates an inline for the target filesourceMap
// "declarationMap": true// Generate the declaration filesourceMap
// "typeRoots": [], // declare the file directory, default node_modules/@types //"types": [], // Declare file package //"removeComments": true// Delete comments //"noEmit": true// No output file //"noEmitOnError": true// Do not output files when errors occur //"noEmitHelpers": trueIf the ts-helpers function is disabled, install the ts-helpers function"importHelpers": true// Introduce helper functions via tslib. The file must be module //"downlevelIteration": true, // Implementation of degraded traverser (ES3/5) //"strict": true// Turn on all strict type checking //"alwaysStrict": false// Inject into the code"use strict";
// "noImplicitAny": false, // Implicit any type is not allowed"strictNullChecks": false// It is not allowed to assign null and undefined to other types of variables"strictFunctionTypes": false// Bidirectional covariance of function arguments is not allowed"strictPropertyInitialization": false// Class instance attributes must be initialized //"strictBindCallApply": false// Strictbind/ / / call/apply inspection"noImplicitThis": false// This is not allowed to have an implicit any type //"noUnusedLocals": true// Check that only unused local variables are declared //"noUnusedParameters": true// Check for unused function arguments //"noFallthroughCasesInSwitch": true// Prevent switch statements from running through //"noImplicitReturns": true// Each branch must return a value //"esModuleInterop": true, / / permitexport = 导出,由import from 导入
// "allowUmdGlobalAccess": true// Allow access to UMD global variables in modules //"moduleResolution": "node"// Module parsing policy //"baseUrl": ". /"// Resolve the base address of non-relative modules //"paths": {// path mapping, relative to baseUrl //"jquery": ["node_modules/jquery/dist/jquery.slim.min.js"], / / / /}"rootDirs": ["src"."out"], // Put multiple directories in a single virtual directory for runtime //"listEmittedFiles": true// Print out the output file //"listFiles": true, // Prints the compiled file (including the referenced declaration file)}}Copy the code
Compile tools
ts-loader:
- Compile typescript into javascript
- TranspileOnly: only language conversion is performed without type check.
- Fork-ts-checker -webapck-plugin separate type checking process.
awesome-typescript-loader
Main differences with TS-Loader:
- Better for integration with Babel, using Babel escape and caching.
- Type checking can be done in a separate process without the need to install additional plug-ins.
Why use Babel when you use TypeScript?
Both TSC and Babel have language conversion capabilities. The difference is that TSC has code checking while Babel does not, but Babel has rich plug-ins.
Babel: a plug-in that only does language conversion
- @babel/preset-typescript
- @babel/proposal-class-properties
- @babel/proposal-object-rest-spread
You can read this article:
Juejin. Cn/post / 684490…