The foreword 0.
How to… Hey, don’t get it wrong, this is a pure technical article.
0.1 according to the TypeScript
What? Uvu to change Vue 3.0 to Typescript entirely? Are you kidding me? Should I be writing Vue apps in TypeScript?
Well, Vue3.0 probably won’t come out until the end of the year at the soonest. It will be more user-friendly for Ts, not just for Ts, especially because of static type detection and Ts performance better than Flow. Since the big leap to open source, there are a lot of new tools on the front end like VS Code and TypeScript. I think TypeScript really took off because of the increasing complexity of front-end applications, which leads to poor maintainability and scalability. Especially when writing class libraries, it is necessary to consider the reuse and extensibility of each class and method, so design patterns will be used to optimize the code. More importantly, coding efficiency is improved, and static systems definitely reduce bug debugging time.
0.2 Advantages & Disadvantages
advantages
-
The static type system, with the help of the compiler to handle errors during compilation, avoids errors that may occur at runtime in advance and improves the reliability of code virtually.
-
Second, if data types are identified in the program, the compiler can optimize the program for that information. Typescript is compiled into JavaScript and optimized for JS’s basic data types.
-
There are a lot of tools in the community, VS Code support is great, type hints and Reference tags are great, and developer tools and experiences are well done in the JS world.
disadvantages
-
Learning curve, there may be an adaptation period for programmers who do not have a background in static languages such as Java/C++.
-
Typescript as a statically typed language requires programmers to write programs by contract that specify a type for each variable. In addition to Javascript’s basic types such as string and number, Typescript uses the Interface keyword to declare a type for composite structures.
-
The declaration of a type adds more code, and these details distract the programmer from the business logic during programming.
let foo = 123;
foo = '456'; // Error: cannot assign `string` to `number
Copy the code
- TypeScript supports new features in ES2015+. As standards evolve, new features are added to TypeScript, which can be compiled to avoid the risk of using new features in older browsers.
1. Engineering practice
1.1 Generic WebPack configuration
Webpack has been released in version 4.41, and many of you are already using Webpack4. The support for typescript in WebPack4 is also wrong. The biggest changes are the “zero configuration” and the incorporation of commonChunks into Webpack. Latest version:
- The first step is to install TypeScript. TypeScript is a superset of JavaScript that has a lot of features or syntactic sugar that aren’t native to JavaScript, and the browser can’t run it directly. There’s a compilation process that converts TypeScript into JavaScript. Install typescript first
npm install -g typescript
Copy the code
- Then try compiling. Once installed locally, you can compile a.ts file and output it as a standard JavaScript file. Suppose we have a Student class written in TypeScript.
class Student {
private name: string;
constructor(name: string) {
this.name = name; }}Copy the code
Compile it using the typescript Compiler
tsc student.ts
Copy the code
The result is a standard JavaScript file generated based on the compile options.
var Student = /** @class */ (function () {
function Student(name) {
this.name = name;
}
returnStudent; } ());Copy the code
- Command-line compilation works for a single or small number of typescript files. If you want to write large applications or class libraries in typescript, you need to configure WebPack to automatically compile the entire project at build time. Configuring TypeScript projects with Webpack follows the following process:
TypeScript--> Javascript version of ES Next --> More compatible Javascript.Copy the code
It is worth noting that
Previously installed TypeScript Compiler, compiler Option usually specifies that TypeScript is compiled into JavaScript versions that support ES5/ES6/ES Next. But in practice we need to do another translation using Babel’s results for two reasons.
- TypeScript compiles cannot be used directly in production. Babel allows you to translate production-compatible JS code through BrowserList.
- Babel can introduce polyfills, often with TypeScript compilation targets set to ES Next, and Babel can then introduce polyfills as needed to keep the resulting JS code volume to a minimum.
const path = require('path')
const webpack = require('webpack')
const config = {
entry: './src/index.ts'.module: {
rules: [{// ts-loader: convert typescript to javascript(esnext),
// babel-loader: converts javascript(esnext) to javascript(backward compatibility)
test: /\.(tsx|ts)? $/.use: ['babel-loader'.'ts-loader'].exclude: /node_modules/}},],resolve: {
extensions: ['.tsx'.'.ts'.'.js'].alias: {
The '@': path.resolve(__dirname, './src'),
'mobx': path.resolve(__dirname, './node_modules/mobx/lib/mobx.es6.js')}}}Copy the code
1.2 Typescript compiler configuration
A quick look at typescript compilation options, which specify the JS version to compile, how the code should be modularized, and how the code should be checked.
-
AllowJS indicates whether JavaScript files are allowed to be compiled.
-
Target represents the target version of ECMAScript, such as’ ESNext ‘, ‘ES2015’.
-
Module means modular, such as’ commonJS ‘, ‘umD’ or ‘ES2105 ‘(es module)
-
ModuleResolution is the strategy for module resolution that tells the compiler where to find the current module. When specified as ‘node’, nodeJS’s module resolution strategy is adopted. The full algorithm can be found in Node.js Module documentation; When it is specified as ‘classic’, TypeScript’s default parsing strategy is used, which is primarily intended to be compatible with older versions of TypeScript.
-
Strict Indicates whether all strict type-checking types are enabled, including ‘noImplicitAny’, ‘noImplicitThis’, etc.
-
Lib indicates the list of library files to be imported during compilation, depending on actual application scenarios.
-
ExperimentalDecorators is to support the option of decorator syntax, which needs to be enabled because Mobx is used for state management in the project.
-
The include option represents the directory to compile
-
OutDir indicates the directory where the compilation result is output.
{
"compileOnSave": true."compilerOptions": {
"target": "esnext"."module": "esnext"."moduleResolution": "node"."sourceMap": true."strict": true."allowJs": true."experimentalDecorators": true."outDir": "./dist/"."lib": [
"es2015"."dom"."es2016"."es2017"."dom.iterable"."scripthost"."webworker"]},"include": [
"src/**/*.ts"]}Copy the code
1.3 tslint practice
Tslint is a Lint tool for typescript. Like ESLint following Airbnb Style or Standard Style, ESLint can also specify which typescript specification to follow. Recommended, Latest, and all, saving us the trouble of configuring every rule in TSLint.
-
Recommended is a stable version of a rule set that is good to use in normal typescript projects, following SemVer.
-
Latest is constantly updated to include the configuration of the latest rule in each TSLint version, and once TSLint releases break change, this configuration is updated as well.
-
All Sets all rules to the strictest configuration.
Tslint rules
Tslint rules have severity levels, and each rule can be configured with default Error Warning or OFF. Tslint presets provide a lot of rules that can be distilled from code practice, and I think there are a few rules that we encounter frequently or need to keep an eye on.
-
Only arrow functions are allowed. Traditional function expressions are not allowed.
-
Promise-function-async Any function or method that returns a promise should be identified with ‘async’;
-
Await-promise will warn us if the value followed by the ‘await’ keyword is not a promise, regulating how we write asynchronous code.
-
No-console disallows the use of the ‘console’ method in code, making it easier to remove useless debugging code.
-
No-debugger disallows the use of ‘debugger’ methods in code, ditto.
-
No-shadowed-variable When variables with the same name exist in the local and outer scopes, the name is shadowing. As a result, the local scope cannot access variables with the same name in the outer scope.
-
No-unused -variable Indicates the unused variable, import, or function that cannot exist. The point of this rule is to avoid compilation errors, but also to confuse the reader by declaring variables that do not apply.
-
Max-line-length specifies the maximum number of words per line.
-
Quotemark specifies the symbol to use for string constants, typically specifying ‘single’; It depends on the style of the team.
-
Prefer-const whenever possible declare variables with ‘const’ instead of ‘let’. For variables that will not be assigned repeatedly, ‘const’ is used by default;
Other rules you can see in the tsLint documentation for more details. Using Lint allows you to better standardize code styles, keep teams consistent, avoid compilation errors, and improve readability and maintainability.
Special flags for TSLint
When writing ts code, we often encounter a line of code that is too long. In this case, we can use flags provided by TSLint to make the line not subject to rules.
// tslint:disable-next-line:max-line-length
private paintPopupWithFade<T extends THREE.Object3D>(paintObj: T, popupStyleoption: PopupStyleOption, userDataType: number) {
/ /...
}
Copy the code
Tslint: disable-next-line: rulex tsLint: disable-next-line: rulex tsLint: disable-next-line: rulex
2. Typescript Type system Tips
2.1 “Duck” type
“Duck” type?? (Black question mark), when I first saw this noun, I was also confused. In fact, it refers to structural type, and at present type detection is mainly divided into structural type and nominal type.
interface Point2D {
x: number;
y: number;
}
interface Point3D {
x: number;
y: number;
z: number;
}
var point2D: Point2D = { x:0, y: 10}
var point3D: Point3D = { x: 0, y: 10, z: 20}
function iTakePoint2D(point: Point2D) { /*dosth*/ } iTakePoint2D(point2D); // Type match iTakePoint2D(point3D); ITakePoint2D ({x:0}); // Error: missing information 'yCopy the code
The difference between
-
In structural types, type detection and judgment are based on the structure of the type, which attributes it has, and which types it is. Not the name of the type or the ID of the type.
-
Nominal types are used by static languages such as Java and C, which simply means that if two types have different type names, they are different types, even though they have the same structure.
-
Typescript types are structural types. Type checking focuses on the shape of the value, duck typing, and defining types through interfaces is about defining shapes and constraints. So defining interfaces is about defining new types for structures. To Typescript, two types are the same type as long as they have the same structure.
2.2 Type Identification/Classification
Knowing that typescript is a ‘duck type’ leads to the question of how typescript can be identified as a duck type, as in this example:
public convertString2Image(customizeData: UserDataType) {
if (Helper.isUserData(customizeData)) {
const errorIcon = searchImageByName(this.iconImage, statusIconKey);
if (errorIcon) {
(customizeData asUserData).title.icon = errorIcon; }}else if (Helper.isUserFloorData(customizeData)) {
// do nothing
} else {
// UserAlertData
let targetImg;
const titleIcon = (customizeData asUserAlertData)! .title.icon;if (targetImg) {
(customizeData asUserAlertData).title.icon = targetImg; }}return customizeData;
}
Copy the code
CustomizeData is the user data. At this time, we need to call searchImageByName method to load the corresponding image according to different types. So we need to use some type determination method to determine the type of the object at run time.
Basic type judgment
We might think of typeof and instanceof as basic types. In TS, we can also use these two operators to determine types, such as:
- use
typeof
Determine type
function doSomething(x: number | string) {
if(typeof x === 'string') {
console.log(x.toFixed()); // Property 'toFixed' does not exist on type 'string'
console.log(x.substr(1));
} else if (typeof x === 'number') {
console.log(x.toFixed());
console.log(x.substr(1)); // Property 'substr' does not exist on type 'number'.}}Copy the code
As you can see, it is possible to use Typeof to determine base data types at run time. You can perform different business logic for different types in different conditional blocks, but for non-base types defined by Class or Interface, you must consider other ways.
- use
instanceof
The following example determines the type based on the incominggeo
The types of objects perform different processing logic:
public addTo(geo: IMap | IArea | Marker) {
this.gisObj = geo;
this.container = this.draw()! ;if (!this.container) {
return;
}
this.mapContainer.appendChild<HTMLDivElement>(this.container!) ;if (this.gisObj instanceof IMap) {
this.handleDuration();
} else if(this.gisObj instanceof Marker) {
//}}Copy the code
As you can see, it is possible to use Instanceof to determine types dynamically, and types can be types declared by the Class keyword, all of which have complex structures and have constructors. In general, there are two conditions for determining a type using instanceof:
- It must be a type that has a constructor, such as a class type.
- The constructor
prototype
The attribute type cannot beany
.
Use type predicates to determine types. So with the first example, we’re going to determine a duck type. In TS, we have a special way of doing this, which is the concept of type predicates, typescript’s type protection mechanism, which checks at runtime to ensure that types are in a particular scope. For the types defined and mapped by the Interface, which does not have constructors, we need to define our own checking methods for that type, also known as type protection.
The implementation of the two type-protected methods called in the example
public static isUserData(userData: UserDataType): userData is UserData {
return ((userData asUserData).title ! = =undefined) && ((userData asUserData).subTitle ! = =undefined)
&& ((userData asUserData).body ! = =undefined) && ((userData as UserData).type === USER_DATA_TYPE.USER_DATA);
}
public static isUserFloorData(userFloorData: UserDataType): userFloorData is UserFloorData {
return ((userFloorData asUserFloorData).deviceAllNum ! = =undefined)
&& ((userFloorData asUserFloorData).deviceNormalNum ! = =undefined)
&& ((userFloorData asUserFloorData).deviceFaultNum ! = =undefined)
&& ((userFloorData asUserFloorData).deviceOfflineNum ! = =undefined);
}
Copy the code
In fact, to determine the structure of the type, which is why the ts type system is called a duck type, we need to traverse every property of the object to distinguish the type. In other words, if two types with identical structures are defined, they are judged to be the same type, even if the type names are different
2.3 What are index types used for?
Index types, which enable the compiler to examine code that uses dynamic attribute names. Ts uses the index access operator keyof to get the attribute name of a type, as in the following example:
function pluck<T.K extends keyof T> (o: T, names: K[]) :T[K] []{
return names.map(n= > o[n]);
}
interface Person {
name: string;
age: number;
}
let person: Person {
name: 'Jarid',
age: 35
}
let strings: string[] = pluck(person, ['name']);
Copy the code
The principle compiler checks if name is really an attribute of Person, and then keyof T, the index type query operator, results in a union of known attribute names on T for any type T.
let personProps: keyof Person; // 'name' | 'age'
Copy the code
That is, the property name can be any interface type!
Index access operator T[K]
The index type refers to the fact that an attribute in TS can be dynamically typed, and the type is known at runtime when it is evaluated. You can use the T[K] type in a normal context, just make sure that K extends Keyof T, as in the following:
function getProperty<T.K extends keyof T> (o: T, name: K) :T[K] {
return o[name];
}
Copy the code
When you return the result of T[K], the compiler instantiates the true type of the key, so the type of the return value of getProperty will change as you change the desired property.
let name: string = getProperty(person, 'name');
let age: number = getProperty(person, 'age');
let unknown = getProperty(person, 'unknown'); // error, 'unknown' is not in 'name' | 'age'
Copy the code
Index types and string index signatures keyof and T[k] interact with string index signatures. Such as:
interface Map<T> {
[key: string]: T; // This is a type with a string index signature, keyof T is string
}
let keys: keyof Map<number>; // string
let value: Map<number> ['foo']; // number
Copy the code
Map
is a type with a string index signature, so keyof T would be string.
2.4 Mapping Types
Background There is a problem with typescript that we can’t get around – how to create new types from old ones, called mapping types.
interfacePersonPartial { name? :string; age? :number;
}
interface PersonReadonly {
readonly name: string;
readonly age: number;
}
Copy the code
As you can see, the PersonReadOnly type is only read-only for fields of the PersonParial type. Imagine if this type had 10 fields and you would have to write those 10 fields repeatedly. Is there any way we can get new types by mapping instead of repeating boilerplate code? The answer is the mapping type,
The new type converts each property of the old type in the same way:
type Readonly<T> {
readonly [P in keyof T]: T[P];
}
Copy the code
Its syntax is similar to that of index signatures, with three steps:
- The type variable K is bound to each property in turn.
- String literal associative
Keys
Contains a collection of property names to iterate over - The type of the property.
Take this example
type Keys = 'option1' | 'option2';
type Flags = { [K in keys]: boolean };
Copy the code
Keys is a hard-coded string of attribute names, and the type of the attribute is Boolean, so this mapping type is equivalent to:
type Flags = {
option1: boolean;
option2: boolean;
}
Copy the code
Typical uses We will often encounter or more commonly use (generic writing) :
type Nullable<T> = { [P in keyof T]: T[P] | null }
Copy the code
Declare a Person type. Once converted using a Nullable type, each property of the new type is Nullable.
// test
interface Person {
name: string;
age: number;
greatOrNot: boolean;
}
type NullPerson = Nullable<Person>;
const nullPerson: NullPerson = {
name: '123',
age: null,
greatOrNot: true};Copy the code
Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit
export interface Product {
id: string;
name: string;
price: string;
description: string;
author: string;
authorLink: string;
}
export type ProductPhotoProps = Pick<Product, 'id' | 'author'| 'authorlink' | 'price'>;
// Omit Omit material
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type ProductPhotoOtherProps = Omit<Product, 'name' | 'description'>;
Copy the code
We can pick several types from existing Product types to form a new type; It is also possible to omit several types and form the rest of the attributes into new types.
benefits
- Keyof T returns a list of attributes of T. T[P] is the result type. This type conversion does not apply to other attributes on the stereotype chain, meaning that the mapping applies only to the attributes of T and not to other attributes on the stereotype chain. The compiler copies all existing property modifiers before adding new properties.
- Either attributes or methods can be mapped.
2.5 Never vs Void
First, there are two scenarios of the never type:
- A function that never returns a value when returned by a function.
- Represents a function that always throws an error.
// 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");
}
Copy the code
Void Void also has its applications
- When a function does not return a value, typescript automatically considers its return value
void
. - Declare it in code
void
The type or return value is marked asvoid
This improves the readability of the code, makes it clear that the method does not return a value, and avoids paying attention to the return value when writing tests.
public remove(): void {
if (this.container) {
this.mapContainer.removeChild(this.container);
}
this.container = null;
}
Copy the code
summary
never
The essence represents the types of values that never exist. It can also represent the return value of a function expression or arrow function expression.- We can define a function or a variable as theta
void
Type, variables can still be assignedundefined
ornull
, butnever
Yes can only be returned with a value ofnever
Is assigned to the function.
2.6 Enumeration Types
Ts uses the enum keyword to define enumeration types. It seems that enumeration exists in many strongly typed languages, but Javascrip does not. Enumeration can help us better replace the magic number or value with meaningful names that often appear in code. Here is an enumeration type that we use in our business:
export enum GEO_LEVEL {
NATION = 1,
PROVINCE = 2,
CITY = 3,
DISTRICT = 4,
BUILDING = 6,
FLOOR = 7,
ROOM = 8,
POINT = 9,}Copy the code
Because the values are numbers, they are also commonly referred to as numeric enumerations.
Enumerations are numeric based. Values can be assigned to enumerations such as:
enum Color {
Red,
Green,
Blue
}
var col = Color.Red;
col = 0; // Have the same effect as color. Red
Copy the code
Let’s look at how an enumeration of a numeric type can be converted to JavaScript:
// Translated Javascript
define(["require"."exports"].function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var GEO_LEVEL;
(function (GEO_LEVEL) {
GEO_LEVEL[GEO_LEVEL["NATION"] = 1] = "NATION";
GEO_LEVEL[GEO_LEVEL["PROVINCE"] = 2] = "PROVINCE";
GEO_LEVEL[GEO_LEVEL["CITY"] = 3] = "CITY";
GEO_LEVEL[GEO_LEVEL["DISTRICT"] = 4] = "DISTRICT";
GEO_LEVEL[GEO_LEVEL["BUILDING"] = 6] = "BUILDING";
GEO_LEVEL[GEO_LEVEL["FLOOR"] = 7] = "FLOOR";
GEO_LEVEL[GEO_LEVEL["ROOM"] = 8] = "ROOM";
GEO_LEVEL[GEO_LEVEL["POINT"] = 9] = "POINT";
})(GEO_LEVEL = exports.GEO_LEVEL || (exports.GEO_LEVEL = {}));
});
Copy the code
It’s very interesting, but let’s think about it from a different point of view. The above code actually says something like this:
console.log(GEO_LEVEL[1]); // 'NATION'
console.log(GEO_LEVEL['NATION']) / / 1
// GEO_LEVEL[GEO_LEVEL.NATION] === GEO_LEVEL[1]
Copy the code
So we can use the enumeration variable GEO_LEVEL to convert subscript enumerations to key enumerations, and key enumerations to subscripts.
3. Reference
design pattern in typescript
typescript deep dive
tslint rules
Typescript Chinese documents
Typescript advanced types
you might not need typescript
advanced typescript classes and types