This article is perfect for those of you who are new to typescript and find it hard to learn, have been discouraged by the official documentation. I started typescript in 2018, so I’ll use the thinking I learned as a starting point.

All the following code comments are jsDoc comments, vscode, Atom or wechat developer tools, in the written function or variable declaration and press enter to come out, pay attention to the editor prompt, other details are not mentioned here.

Based on article

First of all, if you know JS, you know TS. Why? Because you can write ts in.ts without defining a type, just like you would write JS, and whatever line of code gets red in the editor, you add a type to it, and that’s basically what TS looks like. Don’t look at the document, write first, because the editor will have a smart prompt, just follow it.

1. When do I need to define a type

Let’s start with a code snippet

/** * gets the date value (weekdays and weekends) */
function getValue() {
    const day = new Date().getDay();
    let value;
    if (day === 0 || day === 6) {
        value = "Weekend";
    } else {
        value = day;
    }
    return value;
}
Copy the code

As you can see, this is plain JS code, as well as standard TS code. Then place the mouse over the getValue function name to get a hint of the type returned, like this:

This function returns either a string or a number type. This is one of the intelligent hints of TS type detection, which can infer the type of the following code from static code. Let’s look at one more piece of code

/** * format price *@param The value price * /
function formatPirce(value) {
    return value.toFixed(2);
}
Copy the code

Hover the mouse over toFixed, it will prompt any, and the editor will say red, like this

If you hover over the value in red, you’ll see a quick fix. If you’re writing TS for the first time, try clicking on it and you’ll get the following code

function formatPirce(value: number) {
    return value.toFixed(2);
}
Copy the code

Editor for you to complete these basic operations, not ts does not matter, can use the editor on the line. All you do is tell the editor what type to prompt you for next, and if there is a type prompt, there will be an error detection code. This is the core of TS, remember this, and write TS.

When exactly do you need to declare a type? There are two things:

When the editor is red

When type inference needs to be determined

Moving on to the code snippet

/** * change the number of seconds to the minute/second format *@param The value of seconds *@param WithDay Whether to count the number of days */
function formatSecond(value: number, withDay = false) {
    let day = Math.floor(value / (24 * 3600));
    let hour = Math.floor(value / 3600) - day * 24;
    let minute = Math.floor(value / 60) - (day * 24 * 60) - (hour * 60);
    let second = Math.floor(value) - (day * 24 * 3600) - (hour * 3600) - (minute * 60);
    if(! withDay) { hour = hour + day *24;
    }
    / / format
    day = day < 10 ? ("0" + day).slice(-2) : day.toString();
    hour = hour < 10 ? ("0" + hour).slice(-2) : hour.toString();
    minute = ("0" + minute).slice(-2);
    second = ("0" + second).slice(-2);
    return { day, hour, minute, second }
}
Copy the code

So what I’m doing here is converting the numeric type and eventually completing the 2-bit string and returning it, but in the editor normal JS writing would be red, like this

The reason is that when the default types of day, hour, minute, and second are not declared at the beginning, TS concludes that the four variables are of the type number. As a result, the type detection fails when the following types are changed. In this case, it is necessary to declare the types for the initialized variables, and the final display is like this

/** * change the number of seconds to the minute/second format *@param The value of seconds *@param WithDay Whether to count the number of days */
function formatSecond(value: number, withDay = false) {
    let day: number | string = Math.floor(value / (24 * 3600));
    let hour: number | string = Math.floor(value / 3600) - day * 24;
    let minute: number | string = Math.floor(value / 60) - (day * 24 * 60) - (hour * 60);
    let second: number | string = Math.floor(value) - (day * 24 * 3600) - (hour * 3600) - (minute * 60);
    if(! withDay) { hour = hour + day *24;
    }
    / / format
    day = day < 10 ? ("0" + day).slice(-2) : day.toString();
    hour = hour < 10 ? ("0" + hour).slice(-2) : hour.toString();
    minute = ("0" + minute).slice(-2);
    second = ("0" + second).slice(-2);
    return { day, hour, minute, second }
}
Copy the code

Type declaration is basically these operations, the official documentation tutorial always when each variable declaration or function declaration type declarations, easily lead to beginners understand confusion, actually understand, according to the above several operations might be much simpler, where ts to red, where we will tell it to do the corresponding processing.

2. Enumeration of strings

Example: Look at a code snippet when a function needs to specify fields as parameters when passing parameters

/** * Get the user permission value *@param type 
 */
function getUserValue(type: "amdin" | "staff" | "developer") {
    let value = 0;
    switch (type) {
        case "amdin":
            value = 1;
            break;
        case "staff":
            value = 2;
            break;
        case "developer":
            value = 3;
            break;
    }
    return value;
}
Copy the code

When you call the getUserValue function, you will be prompted for the three options enumerated above when you enter “” as the first argument, and the first argument should be only those three arguments, or red if it is any other. This is the function I most often use and rely on. For example, when I use the interface to pass parameters, I declare the enumeration field for the first time. After that, I don’t need to look at the document or ask the background, and I will directly prompt the corresponding parameter fields.

Similarly, variable declarations can also use field enumerations, like this

let skill: "javascript" | "java" | "php";

// When you call this variable elsewhere, you get an enumeration of good hints
if (skill === "java") {}Copy the code

This enumeration not only has hints, but also code execution logic hints, like this

3. The interface

Again, look at the code first and summarize the description later

/** * Set user information *@param Info Information */
function setUserInfo(info: { token: string, id: number | string, phone: number | "" }) {
    // do some...
}
Copy the code

Here we define a function. The first parameter is an object with attributes such as token, ID, and phone. If there are N more attributes, the function will become very ugly. Now let’s see what it looks like when it’s pulled out by the interface

// The interface can declare object attributes without commas

/** User information type */
interface UserInfo {
    /** User login credentials */
    token: string
    /** User id */
    id: number | string
    /** * The mobile phone number to be bound to the client *@description Note that the phone number may be empty, use */
    phone: number | ""
}

/** * Set user information *@param Info Information */
function setUserInfo(info: UserInfo) {
    // do some...
}
Copy the code

The interface and type functions are the same, but are written slightly differently. Replace the above code with type

/** User information type */
type UserInfo = {
    /** User login credentials */
    token: string
    /** User id */
    id: number | string
    /** * The mobile phone number to be bound to the client *@description Note that the phone number may be empty, use */
    phone: number | ""
}
Copy the code

As you can see, it’s more intuitive to use the interface to pull out the type definition, and you can add annotations to it (jsDoc annotations), and then hover over the corresponding variable to get a comment prompt for the corresponding definition, like this

Note that functions can also use interfaces to declare types, and interfaces can also inherit extends, as in class

Instead, let’s see how the following code fetches the interface key, or is it based on the code above

const userInfo: UserInfo = {
    token: "".id: "".phone: ""
}

function getUserInfoValue(key: "token" | "id" | "phone") {
    return userInfo[key];
}
Copy the code

Suppose I want to define the key value of the getUserInfoValue function in which the key parameter field is userInfo, the general operation would be like this. But if UserInfo has N keys, you can’t enumerate them all, so you need to use keyof, like this

function getUserInfoValue(key: keyof UserInfo) {
    return userInfo[key];
}
Copy the code

Another common operation is this

for (const key in userInfo) {
    userInfo[key as keyof UserInfo] = "";
}
Copy the code

Key = as keyof UserInfo Because key defaults to string in the for in loop, this is fine when you write js, but in TS you must specify any, so you need to specify the type. UserInfo [key] = “”; Red: “The element implicitly has type “any” because expressions of type “string” cannot be used with index type “UserInfo”. Could not find index signature with parameter of type “string” on type “UserInfo”

Type assertion

Sometimes we know that a value is of a specified type, but when ts is red, we assign the corresponding type to it. Like this,

const el = document.querySelector(".box");

el.textContent = "Modified Content";

Copy the code

Why is EL still in red when there is no problem

So if I mouse over EL it’s probably null, but we know that box is definitely there, it’s not null, so I’m going to use an assertion like this

const el = document.querySelector(".box") as HTMLElement;

el.textContent = "Modified Content";
Copy the code

Now it works, and el can use the corresponding type hint later; There are similar cases

// Global variables defined temporarily or for debugging purposes
window.version = "1.0.1";
Copy the code

So the window will be red, and you can use your own operation to know what the corresponding prompt is. It is then used with assertions

// Global variables defined temporarily or for debugging purposes
(window as any).version = "1.0.1";
Copy the code

Any type: any; I haven’t mentioned it before because it’s used in very few scenarios, and it’s rarely used once you’re familiar with TS, and I don’t recommend it unless you really don’t need type checking.

By understanding the above four points, you can basically deal with daily operations

5. How do I get the desired type

Look at the code

const input = document.querySelector(".input") as HTMLElement;
input.value = "xxx"; 
Copy the code

Value: input. Value is red, and mouse over it: HTMLElement does not have an attribute “value” on it. Because the type is not accurate enough, how can I get the exact type of this input? It can be

const el = document.createElement("input");
Copy the code

And then over el you can see the prompt type, like this

HTMLInputElement to the right of the colon is the exact type, and copy it to the code that is red above

const input = document.querySelector(".input") as HTMLInputElement;
input.value = "xxx"; 
Copy the code

This time, no error is reported, and the mouse over input.value will get the correct type

Same thing with functions, let’s do a slightly more complicated example

function request() {
    return new Promise(function (resolve, reject) {
        // Here is an interface request operation
        const res = {
            code: 1.data: {},
            msg: "success"
        }
        resolve(res)
    })
}
Copy the code

Because the interface request is based on a method, I’m going to constrain the data for each interface response into a uniform format and return it; Let’s see what it looks like when it’s called

// when called elsewhere
request().then(res= > {
    console.log(res);
})
Copy the code

As you can see, when writing res, the type format defined by the above code is not intelligently prompted. Hover the mouse over RES to see the format

As we did above to get HTMLInputElement, let’s hover the mouse over the request method and see what is prompted

Then copy and paste, define an interface type, and the final code looks like this

/** Interface response type */
interface ApiResult {
    /** * request status code, 'code === 1' for success */
    code: number
    /** Interface response data */
    data: any
    /** Interface response description */
    msg: string
}

function request() :Promise<ApiResult> {
    return new Promise(function (resolve, reject) {
        // Here is an interface request operation
        const res = {
            code: 1.data: {},
            msg: "success"
        }
        resolve(res)
    })
}
Copy the code

If you hover over the res variable at the call location, you’ll see the type hint. If you’re careful, you can write

directly after the Promise, which is generic, like this

function request() {
    return new Promise<ApiResult>(function (resolve, reject) {
        // Here is an interface request operation
        const res = {
            code: 1.data: {},
            msg: "success"
        }
        resolve(res)
    })
}
Copy the code

The two methods are the same, the specific project practice operation can refer to vue-admin; Can’t write type doesn’t matter, will carefully look for rules to see the editor code prompt on the line; Also can refer to some third-party libraries, see how others write type, try Ctrl+ mouse click method or property, is to jump to the corresponding code position. Tips: There are also type hints in javascript, along with jsDoc comments.

Advanced article

1. The generic

The description of generics on the official website is very simple, directly as it is defined in Java to explain, here we learn javascript, so it should be understood and described in javascript thinking;

Generics equals dynamic typing; The definition of generics is usually defined by T, see the code snippet

Suppose we were to write aforEachTo iterate over a group of numbers, js looks like this

/** * 'forEach'; /** * 'forEach'@param {Array} * the list array@param {Function} Fn iterates the function */
function forEach(list, fn) {
    for (let i = 0; i < list.length; i++) {
        const item = list[i];
        typeof fn === "function"&& fn(item); }}Copy the code

I’m going to make ts+ generics look like this

/** * 'forEach'; /** * 'forEach'@param * the list array@param Fn iterates the function */
function forEach<T> (list: Array<T>, fn: (item: T) => void) {
    for (let i = 0; i < list.length; i++) {
        constitem = list[i]; fn(item); }}Copy the code

So what do generics do

const options = [
    { id: 1.label: "Tag 1".on: true }, 
    { id: 2.label: "Tag 2" }, 
    { id: 3.label: "Tag 3" } 
]

forEach(options, function(item) {
    item.
})
Copy the code

When you call an item, or when you mouse over an item, you know what type the item is, what properties it has, what types are they

Further details is that you pass in what type, the code will make the corresponding type prompt and detection according to the corresponding type; Generics can be any type, so I’m just using arrays; Generics can also do some complicated recursive processing, as you’ll see in a bit of practice.

2. Basic tool types

Read-only type

Sometimes we define an interface or a type that we don’t want others to change directly, that is, const, but properties in the object don’t have const, so we use read-only properties

/** User information type */
interface UserInfo {
    /** User login credentials */
    readonly token: string
    /** User id */
    readonly id: number | string
    /** * The mobile phone number to be bound to the client *@description Note that the phone number may be empty, use */
    readonly phone: number | ""
}

const userInfo: UserInfo = {
    id: "".token: "".phone: ""
}
Copy the code

I made all the properties read only, so if I change the properties, I will get an error

userInfo.id = "xxx"; // "id" cannot be assigned because it is read-only. ts(2540)
Copy the code

Optional type

Optional type keywords are simpler question marks, right? , the corresponding mandatory type is simpler. The default declaration only needs to leave? It is a must

/** User information type */
interface UserInfo {
    /** User login credentials */
    token: string
    /** User id */
    id: number | string
    /** * The mobile phone number to be bound to the client *@description Note that the phone number may be empty, use */phone? :number | ""
}

const userInfo: UserInfo = {
    id: ""./ / will be selected
    token: ""./ / will be selected
    // phone: "" // phone can be left out because it is optional
}
Copy the code

I’m going to apply it to the function

function getUsrInfo(info? : UserInfo,type? :string) {
    // do some ...
}
Copy the code

Deep tool class

Now there is a feature: suppose I use the UserInfo interface above and I want each of its properties to be read-only, then it might be written like this

interface UserInfo {
    readonly token: string
    readonly id: number | string
    readonly phone: number | ""
}
Copy the code

But there’s a problem, if the interface has a lot of attributes, and the subattributes of attributes have a lot of layers, then this manual keyword operation is a little bit low. Back to what I said above, generics can operate on types dynamically, so we can write some recursive operations like this

/** Deep recursion all attributes are read-only */
export type DeepReadonly<T> = {
    readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
}
Copy the code

When using

interface UserInfo {
    /** User login credentials */
    token: string
    /** User id */
    id: number | ""
    /** * The mobile phone number to be bound to the client *@description Note that the phone number may be empty, use */
    phone: number | ""
    /** Details */
    info: {
        /** User age */
        age: number | ""
        /** User remarks */
        desc: string}}const userInfo: DeepReadonly<UserInfo> = {
    token: "".id: "".phone: "".info: {
        age: "".desc: ""}}/ / red
userInfo.info.age = 20; // Cannot be assigned to "age" because it is read-only.

Copy the code

This is what we want. We can’t change all the properties of this object. Similarly, we can write one more recursive generic: optional;

/** Deep recursion all attributes are optional */
export type DeepPartial<T> = {
    [P inkeyof T]? : T[P]extends object ? DeepPartial<T[P]> : T[P];
}
Copy the code

Using the above tool types, we can do practical things, such as userInfo cannot be changed directly when called, but can only be changed by a method

Define a utility function to modify the object

/** * Modifies the attribute value - only modifies the existing value *@param Target Modified target *@param Value Modified content */
function modifyData<T> (target: T, value: T) {
    for (const key in value) {
        if (Object.prototype.hasOwnProperty.call(target, key)) {
            // target[key] = value[key];
            // Deep assignment, if necessary
            if (typeof target[key] === "object") {
                modifyData(target[key], value[key]);
            } else{ target[key] = value[key]; }}}}Copy the code

Then define a function that can modify userInfo

/** * Update the user information field *@param value 
 */
function updateUserInfo(value: DeepPartial<UserInfo>) {
    modifyData(userInfo, value);
}
Copy the code

Finally, try modifying the deep read-only userinfo.info.age

updateUserInfo({
    info: {
        age: 20}})Copy the code

I’m changing age separately here and I’m also getting the type hint that comes with generics, which is the desired result;

Here is an aside: Anyone who has used Vuex knows that you can directly modify a store attribute in the state of store, but the official instruction is store.mit to modify the attribute, which can not be restricted to directly modify store.xxx. Now, this is the best solution, refer to my other article you don’t need Vuex

For more advanced tool types, see TypeScript advanced usage. This article only covers the basics.

3. Class Related types

Class is becoming more like Java with the help of TS. Again, we know javascript, so don’t talk about Java

public

class ModuleA {
    // Public is optional, because the default is public
    // Type definitions are just like ordinary variables. They can be declared or not, but must be declared if they are red
    public age = 12;
}
const a = new ModuleA();

// Instantiation is accessible here
a.age / / 12
Copy the code

private

Private attributes are accessible from {} in the current ModuleA, not instantiated or inherited subclasses

class ModuleA {
    
    private age = 12;
}

const a = new ModuleA();

a.age // The editor displays a red message indicating that the property "age" is private and can only be accessed in class "ModuleA".


class ModuleB extends ModuleA {
    constructor() {
        super(a); }getAge() {
        console.log(this.age); // The editor displays a red message indicating that the property "age" is private and can only be accessed in class "ModuleA".}}const b = new ModuleB();

b.age // The editor displays a red message indicating that the property "age" is private and can only be accessed in class "ModuleA".

Copy the code

protected

Protected: Cannot be accessed after instantiation. Other environments can be accessed

class ModuleA {
    protected age = 12;
}

const a = new ModuleA();

a.age // Attribute "age" is protected and can only be accessed in class "ModuleA" and its subclasses.

class ModuleB extends ModuleA {
    constructor() {
        super(a); }getAge() {
        return this.age; // It can be accessed}}const b = new ModuleB();

b.age // Attribute "age" is protected and can only be accessed in class "ModuleA" and its subclasses.
Copy the code

readonly

Like interfaces, calSS has readonly, except that the properties declared by readonly can be modified by constructor, but otherwise cannot, like this:

class ModuleA {
    constructor() {
        this.age = 456;
    }
    
    readonly age: number | "" = "";

    onLoad() {
        this.age = 123; "Age" cannot be assigned because it is read-only.}}const a = new ModuleA();

a.age = 20; "Age" cannot be assigned because it is read-only.

Copy the code

Define dynamic properties or methods

Attributes to use! , how to use? The type of: is simpler

class ModuleA {
    
    /** User information */userInfo! : UserInfo;/** Get user information */getUserInfo? (): UserInfo;onLoad() {
        // Dynamically set methods and properties
        this.getUserInfo = function() {
            return {
                token: "".id: "".phone: "".info: {
                    age: 18.desc: ""}}}this.userInfo = this.getUserInfo(); }}Copy the code

Practice: define dynamic properties or methods | set dynamic methods

Global type declaration

Usually used to do some function module definition, specify the file suffix d.ts; For example, if there is an assertion for window.version, if you don’t want to use any, create a new filename

/ [tutorial] (https://blog.csdn.net/weixin_34289454/article/details/92072706) * * * * /
// declare global {
// interface Window {
// ** The current version, easy to view in the console debugging */
// version: string
/ /}
// }
interface Window {
    /** The current version, easy to view in the console debugging */
    version: string
}
Copy the code

You can also define some custom global variables or functions

/** Interface request return field */
interface ApiResult {
    / * * * / icon
    icon: string
    /** 金额 */
    money: number./ * * - * * AD configuration array ` 0 * - ` ` group 1 ` fully AD * * - ` 2 ` group - ` AD * 3 ` circle@example* * /,0,0,0,0,0,0 [0]
    adConfig: Array<number>
    /** Share a picture */
    shareImg: string
    /** Popup prompt 'HTML' */
    popupTip: string
    /** Corresponding to the prompt field */
    popupText: string
}

declare const api: ApiResult;
Copy the code

You get the type hint when you use it

Almost 90% of the third-party libraries on NPM now support typescript, so when importing or using these libraries, even if you use javascript, there will be type hints because they all define their own type standards. For example, when I use element-UI

Crtl+ mouse click can jump to the corresponding position, at this time there is no need to look at the document to write code, one is the type of the reference, two is to see the detailed use of the corresponding method; So let’s say I want to see how Message is passed, what is passed, like this

There are more detailed use methods than the document, no longer like the past use of javascript, to check a method variable on the global search ah, baidu search ah, do not accurately locate for a long time, the efficiency is very low. Anyone who says typescript doesn’t work doesn’t know it exists. Typescript is well written, and coming back a year or so later to fix a snippet of code saves time, not to mention bugs, than revisiting javascript code.