TypeScript best practices before Vue 3.0

TypeScript best practices before Vue 3.0

Preface

Personally, I don’t have a positive view on stricter type restrictions. After all, I have become accustomed to writing various types of Sao.

Ran Goose’s recent project is TypeScript+ Vue, Mao Jila, Xuezhi... It’s so fragrant!

1. Build with official scaffolding

npm install -g @vue/cli
# OR
yarn global add @vue/cli

The new VueCLItool allows developers to use TypeScriptto create a new project integrated environment.

Just run vue createmy-app.

Then, the command line will ask to select a preset. Use the arrow keys to select Manuallyselectfeatures.

Next, just make sure you select the TypeScriptand Babeloptions, as shown below:

After doing this, it will ask you if you want to use it class-style component syntax.

Then configure the rest of the settings so that it looks like the image below.

The Vue CLI tool will now install all dependencies and set up the project.

Next, let's run the project.

In short, let's run up first.

2. Project directory analysis

By treereview can be found in the directory structure instruction and the normal structure of very different build.

The main concern here shims-tsx.d.tsand shims-vue.d.tstwo files

Two sentence summary:

  • shims-tsx.d.tsAllows you to .tsxthe end of the file, in Vuethe preparation of the project jsxthe code
  • shims-vue.d.tsMainly used TypeScriptto identify .vuefiles, Tsthe default does not support the import vuefile, which tells tsthe import .vuefile by VueConstructor<Vue>processing.

At this time, when we open the cordial one src/components/HelloWorld.vue, we will find that the writing is quite different

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <!-- omitted-->
  </div>
</template>

<script lang="ts">
import {Component, Prop, Vue} from'vue-property-decorator';

@Component
export default class HelloWorld extends Vue {
  @Prop() private msg!: string;
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>

At this point, ready to open a new chapter in TypeScriptspeed entry andvue-property-decorator

## 3. TypeScriptQuick start

3.1 Basic types and extended types

TypescriptAnd Javascriptshare the same basic type, but there are some additional types.

  • Tuple Tuple
  • enumerate enum
  • Any versus Void

1. A collection of basic types

//Numbers, two, eight, and hexadecimal are all supported
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;

//String, both single and double quotes are fine
let name: string = "bob";
let sentence: string = `Hello, my name is ${ name }.

//Array, the second way is to use array generics, Array<element type>:
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];

let u: undefined = undefined;
let n: null = null;

2. Special types

1. TuplesTuple

Imagine tuples as an organized array, you need to predefine the data types in the correct order.

const messyArray = ['something', 2, true, undefined, null];
const tuple: [number, string, string] = [24, "Indrek", "Lasn"]

If you do not follow pre-ordered tuple index rules, it Typescriptwarns.

( tupleThe first item should numbertype)

2. Enumeration enum*

enumType is a supplement to JavaScript standard data types. Like other languages ​​such as C#, enumerated types can be used to give friendly names to a group of values.

//By default, the element number starts from 0, or you can manually start with 1
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;

let colorName: string = Color[2];
console.log(colorName);//output'Green' because its value in the above code is 2

Another good example is the use of enumerations to store application state.

3. Void

In the Typescriptmiddle, you must define the return type of the function . like this:

If there is no return value, an error will be reported:

We can define its return value as void:

Will not be able to return

4. Any

Emmm...It does not matter what type you are dealing with. You can use this when you are not sure what type you are dealing with.

But use it with caution. If you use too much, you lose the meaning of using Ts.

let person: any = "Front-end adviser"
person = 25
person = true

The main application scenarios are:

  1. Access to third-party libraries
  2. Ts dishes are used in the early stage
5. Never

To describe it in superficial words: "It Never's the father you will never get."

The specific behavior is:

  • thrownewError(message)
  • returnerror("Something failed")
  • while(true){}//存在无法达到的终点

3. Type assertion

The simple definition is: it can be used to manually specify the type of a value.

There are two ways of writing, angle brackets and as:

let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;
let strLength: number = (someValue as string).length;

Examples of use are:

When TypeScript is not sure which type the variable of a union type is, we can only access the properties or methods common to all types of this union type:

function getLength(something: string | number): number {
    return something.length;
}

//index.ts(2,22): error TS2339: Property'length' does not exist on type'string | number'.
//Property'length' does not exist on type'number'.

If you access the length, an error will be reported, and sometimes, we do need to access the properties or methods of one of the types when we are not sure about the type. At this time, we need to assert to not report an error:

function getLength(something: string | number): number {
    if ((<string>something).length) {
        return (<string>something).length;
    } else {
        return something.toString().length;
    }
}

Safe navigation operator (?.) and non-empty assertion operator (!.)

Safe navigation operator (?.) and empty attribute path: In order to solve the problem of an error when the page is running when the variable value is null during navigation.

The null hero's name is {{nullHero?.name}}

Non-empty assertion operator:

It is used when it can be determined that the variable value must not be empty.

Unlike the safe navigation operator, the non-empty assertion operator does not prevent null or undefined from appearing.

let s = e!.name;//Assert that e is non-empty and access the name property

3.2 Generic: Generics

A major part of software engineering is to build components. The built components not only need to have clear definitions and unified interfaces, but also need to be reusable. Components that support existing data types and data types added in the future provide great flexibility for the development process of large software systems.

In C#and Javayou can use the "generic" to create reusable components, and a component can support multiple data types. This allows users to use components according to their own data types.

1. Generic methods

In TypeScript, there are two ways to declare generic methods :

function gen_func1<T>(arg: T): T {
    return arg;
}
//or
let gen_func2: <T>(arg: T) => T = function (arg) {
    return arg;
}

There are also two ways to call :

gen_func1<string>('Hello world');
gen_func2('Hello world'); 
//The second calling method can omit the type parameter, because the compiler will automatically identify the corresponding type based on the incoming parameters.

2. Generic and Any

TsSpecial type Anyin the specific use, can be used instead of any type, a quick look nothing like the difference between the two, it is not true:

//Method 1: Method with any parameter
function any_func(arg: any): any {
    console.log(arg.length);
        return arg;
}

//Method 2: Array generic method
function array_func<T>(arg: Array<T>): Array<T> {
      console.log(arg.length);
        return arg;
}
  • A method, a printing argparameter lengthattributes. Because anycan replace any type, so the process is not in an array or pass parameters with lengthtime attribute object will throw an exception.
  • The second method defines the parameter type is Arraya generic type, there will certainly be lengthproperty, so it will not throw an exception.

3. Generic types

Generic interface:

interface Generics_interface<T> {
    (arg: T): T;
}

function func_demo<T>(arg: T): T {
    return arg;
}

let func1: Generics_interface<number> = func_demo;
func1(123);//actual parameters of the correct type
func1('123');//Actual parameter of wrong type

3.3 Custom type: InterfacevsTypealias

Interface, Domestic translation into interface.

Typealias, Type alias.

The following content comes from:

What is the difference between interface and type in Typescript

1. Similarities

Both can be used to describe an object or function:

interface User {
  name: string
  age: number
}

type User = {
  name: string
  age: number
};

interface SetUser {
  (name: string, age: number): void;
}
type SetUser = (name: string, age: number): void;

Both allow extensions:

interfaceAnd typecan be expanded, and the two are not independent of each other, that interfacecan extendstype, typecan extendsinterface. Although the effect is similar, the syntax of the two is different .

interface extends interface

interface Name { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}

type extends type

type Name = { 
  name: string; 
}
type User = Name & {age: number };

interface extends type

type Name = { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}

type extends interface

interface Name { 
  name: string; 
}
type User = Name & { 
  age: number; 
}

2. Differences

typeCan interfacenot

  • type You can declare basic type aliases, union types, tuples and other types
//Basic type alias
type Name = string

//Union type
interface Dog {
    wong();
}
interface Cat {
    miao();
}

type Pet = Dog | Cat

//Specifically define the type of each position of the array
type PetList = [Dog, Pet]
  • typeYou can also use the statement typeoftypes to obtain an instance of assignment
//When you want to get the type of a variable, use typeof
let div = document.createElement('div');
type B = typeof div
  • Other Sao operations
type StringOrNumber = string | number;  
type Text = string | {text: string };  
type NameLookup = Dictionary<string, Person>;  
type Callback<T> = (data: T) => void;  
type Pair<T> = [T, T];  
type Coordinates = Pair<number>;  
type Tree<T> = T | {left: Tree<T>, right: Tree<T> };

interfaceCan typenot

interface Ability to declare merger

interface User {
  name: string
  age: number
}

interface User {
  sex: string
}

/*
The User interface is {
  name: string
  age: number
  sex: string 
}
*/

interface There are optional attributes and read-only attributes

  • Not all attributes in the optional attribute interface are required. Some exist only under certain conditions, or do not exist at all. For example, only some attributes are assigned to the parameter object passed in to the function. Common interface defines the interface with optional attributes, only in a later add optional attribute names defined in the ?symbol. As follows
interface Person {
  name: string;
  age?: number;
  gender?: number;
}
  • As the name implies, the read-only property is not writable, and the value of the object property can only be modified when the object is just created. Before you can use the name attribute readonlyto specify the read-only attribute, as shown below:
interface User {
    readonly loginName: string;
    password: string;
}

The above example shows that the loginName cannot be modified after the initialization of the User object is completed.

3.4 Implementation and inheritance: implementsvsextends

extendsIt is clear that ES6 inside the class inheritance, then implementwhat is it doing? It and extendsWhat is the difference?

implement,achieve. Like the basic function of the interface in C# or Java, TypeScriptit can also be used to explicitly force a class to conform to a certain contract

Basic usage of implement :

interface IDeveloper {
   name: string;
   age?: number;
}
//OK
class dev implements IDeveloper {
    name ='Alex';
    age = 20;
}
//OK
class dev2 implements IDeveloper {
    name ='Alex';
}
//Error
class dev3 implements IDeveloper {
    name ='Alex';
    age = '9';
}

And extendsit is inherited from the parent class, both of which can actually be used mixed with:

class A extends B implements C,D,E

Collocation interfaceand typeusage are:

3.5 Declaration file and namespace: declareandnamespace

Earlier we talked about Vue project shims-tsx.d.tsand shims-vue.d.tsits initial content is this:

//shims-tsx.d.ts
import Vue, {VNode} from'vue';

declare global {
  namespace JSX {
   //tslint:disable no-empty-interface
    interface Element extends VNode {}
   //tslint:disable no-empty-interface
    interface ElementClass extends Vue {}
    interface IntrinsicElements {
      [elem: string]: any;
    }
  }
}

//shims-vue.d.ts
declare module'*.vue' {
  import Vue from'vue';
  export default Vue;
}

declare: When using a third-party library, we need to quote its declaration file to get the corresponding code completion, interface prompts and other functions.

Here are a few commonly used ones:

declare var declare global variables
declare function declare a global method
declare class declare a global class
declare enum declares the global enumeration type
declare global expand global variables
declare module extension module

namespace: "Internal Module" is now called "Namespace"

moduleX{(Equivalent to the currently recommended way of writing namespaceX{)

Collaborate with other JS libraries

Similar module, the same can also be created by using the library namespaces for other JS library .d.tsdeclarations file, such as D3JS library, you can create such a declaration file:

declare namespace D3{
    export interface Selectors {...}
}
declare var d3: D3.Base;

So the above two files:

  • shims-tsx.d.tsIn the global variables globalin a batch renaming a number of internal modules.
  • shims-vue.d.ts, Mean to tell TypeScript *.vuesuffix files can be handed over to vuethe module to handle.

3.6 Access privatemodifiers: public, ,protected

In fact, it is easy to understand:

  1. The default is public
  2. When a member is marked as privatethe time, you can not declare it in the class of external access, such as:
class Animal {
  private name: string;

  constructor(theName: string) {
    this.name = theName;
  }
}

let a = new Animal('Cat').name;//error,'name' is private

protectedAnd privatesimilar, however, protectedmembers can access the derived class

class Animal {
  protected name: string;

  constructor(theName: string) {
    this.name = theName;
  }
}

class Rhino extends Animal {
    constructor() {
         super('Rhino');
   }         
   getName() {
       console.log(this.name)//The name here is the name in the Animal class
   }
}

4. The Vueassembly Tswording

Since vue2.5, vue has better support for ts. According to the official document, vue combined with typescript, there are two ways of writing

Vue.extend

import Vue from'vue'

  const Component = Vue.extend({
     //type inference enabled
  })

vue-class-component

import {Component, Vue, Prop} from'vue-property-decorator'

@Component
export default class Test extends Vue {
  @Prop({ type: Object })
  private test: {value: string}
}

Ideally, Vue.extendthe way of writing is the lowest learning cost. On the basis of the existing writing method, almost zero cost migration.

But Vue.extendmode, and mixinsused in combination. The method defined in mixin will not be recognized by typescript

, Which means that there will be problems such as missing code prompts, type checking, and compilation errors.

The rookie makes the choice, and the big guys pick the best. Let's talk about the second kind directly:

4.1 vue-class-component

We go back to src/components/HelloWorld.vue

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <!-- omitted-->
  </div>
</template>

<script lang="ts">
import {Component, Prop, Vue} from'vue-property-decorator';

@Component
export default class HelloWorld extends Vue {
  @Prop() private msg!: string;
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>

Have written pythonthe students should find familiar:

  • vue-property-decoratorThe official support of the library, provides functions decorator (modifier) Syntax

1. Function Modifiers @

"@" is not so much a modified function as it is a reference and call to the modified function.

Or describe it in a big vernacular @: "The following is surrounded by me."

For example, in the following piece of code, there will be output results if two functions are not called:

test(f){
    console.log("before ...");
    f()
        console.log("after ...");
 }

@test
func(){
    console.log("func was called");
}

Run directly and output the result:

before ...
func was called
after ...

The above code can be seen:

  • Only two functions are defined: testand func, they are not called.
  • If there is no "@test", there should be no output when running.

However, when the interpreter reads the function modifier "@", the following steps will be like this:

  1. To call the testfunction testentry parameters of the function that is called " funcfunction";
  2. testFunction is executed, the inlet parameters (i.e. funcfunction) is called (execution);

In other words, the entry parameter of the function with the modifier is the entire function below. A bit similar to the JavaScripinside of tfunctiona(function(){...});

2. vue-property-decoratorand vuex-classdecorator provided

vue-property-decoratorThe decorator:

  • @Prop
  • @PropSync
  • @Provide
  • @Model
  • @Watch
  • @Inject
  • @Provide
  • @Emit
  • @Component( provided by vue-class-component)
  • Mixins(the helper function named mixins provided by vue-class-component)

vuex-classThe decorator:

  • @State
  • @Getter
  • @Action
  • @Mutation

Let's take a look at the original Vue component template:

import {componentA,componentB} from'@/components';

export default {
    components: {componentA, componentB},
    props: {
    propA: {type: Number },
    propB: {default:'default value' },
    propC: {type: [String, Boolean] },
  }
 //component data
  data () {
    return {
      message:'Hello'
    }
  },
 //calculated attributes
  computed: {
    reversedMessage () {
      return this.message.split('').reverse().join('')
    }
   //Vuex data
    step() {
        return this.$store.state.count
    }
  },
  methods: {
    changeMessage () {
      this.message = "Good bye"
    },
    getName() {
        let name = this.$store.getters['person/name']
        return name
    }
  },
 //life cycle
  created () {},
  mounted () {},
  updated () {},
  destroyed () {}
}

The above template is replaced with modifiers. The writing rule is:

import {Component, Vue, Prop} from'vue-property-decorator';
import {State, Getter} from'vuex-class';
import {count, name} from'@/person'
import {componentA, componentB} from'@/components';

@Component({
    components:{ componentA, componentB},
})
export default class HelloWorld extends Vue{
    @Prop(Number) readonly propA!: number | undefined
  @Prop({ default:'default value' }) readonly propB!: string
  @Prop([String, Boolean]) readonly propC!: string | boolean | undefined

 //original data
  message ='Hello'

 //calculated attributes
    private get reversedMessage (): string[] {
      return this.message.split('').reverse().join('')
  }
 //Vuex data
  @State((state: IRootState) => state. Booking. currentStep) step!: number
    @Getter('person/name') name!: name

 //method
  public changeMessage (): void {
    this.message ='Good bye'
  },
  public getName(): string {
    let storeName = name
    return storeName
  }
   //life cycle
  private created (): void {},
  private mounted (): void {},
  private updated (): void {},
  private destroyed (): void {}
}

As you can see, we have added to that list of life-cycle privateXXXXmethod, because this should not be disclosed to other components.

But not for methodthe reason for private constraints that might be used @Emitto pass information to the parent component.

4.2 Add global tools

To introduce a global module, you need to change main.ts:

import Vue from'vue';
import App from'./App.vue';
import router from'./router';
import store from'./store';

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount('#app');

npm iVueI18n

import Vue from'vue';
import App from'./App.vue';
import router from'./router';
import store from'./store';
//new module
import i18n from'./i18n';

Vue.config.productionTip = false;

new Vue({
    router, 
    store, 
    i18n,//new module
    render: (h) => h(App),
}).$mount('#app');

But this alone is not enough. You need to move src/vue-shim.d.ts:

//Declare global methods
declare module'vue/types/vue' {
  interface Vue {
        readonly $i18n: VueI18Next;
        $t: TranslationFunction;
    }
}

After use this.$i18n()it would not have the error.

4.3 Axios use and packaging

1. Create a new file request.ts

File Directory:

-api
    -main.ts//actual call
-utils
    -request.ts//interface package

2. request.tsFile analysis

import * as axios from'axios';
import store from'@/store';
//Here can be replaced according to the specific UI component library used
import {Toast} from'vant';
import {AxiosResponse, AxiosRequestConfig} from'axios';

/* baseURL is defined according to the actual project*/
const baseURL = process.env.VUE_APP_URL;

/* Create axios instance*/
const service = axios.default.create({
    baseURL,
    timeout: 0,//request timeout
    maxContentLength: 4000,
});

service.interceptors.request.use((config: AxiosRequestConfig) => {
    return config;
}, (error: any) => {
    Promise.reject(error);
});

service.interceptors.response.use(
    (response: AxiosResponse) => {
        if (response.status !== 200) {
            Toast.fail('Request error!');
        } else {
            return response.data;
        }
    },
    (error: any) => {
        return Promise.reject(error);
    });

export default service;

For convenience, we also need to define a fixed set of formats returned by axios and create a new one ajax.ts:

export interface AjaxResponse {
    code: number;
    data: any;
    message: string;
}

3. main.tsInterface call:

//api/main.ts
import request from'../utils/request';

//get
export function getSomeThings(params:any) {
    return request({
        url:'/api/getSomethings',
    });
}

//post
export function postSomeThings(params:any) {
    return request({
        url:'/api/postSomethings',
        methods:'post',
        data: params
    });
}

5. Write a component

In order to reduce time, let's replace it src/components/HelloWorld.vueand make a blog post component:

<template>
    <div class="blogpost">
        <h2>{{ post.title }}</h2>
        <p>{{ post.body }}</p>
        <p class="meta">Written by {{ post.author }} on {{ date }}</p>
    </div>
</template>

<script lang="ts">
import {Component, Prop, Vue} from'vue-property-decorator';

//Type constraints on the data here
export interface Post {
    title: string;
    body: string;
    author: string;
    datePosted: Date;
}

@Component
export default class HelloWorld extends Vue {
    @Prop() private post!: Post;

    get date() {
        return `${this.post.datePosted.getDate()}/${this.post.datePosted.getMonth()}/${this.post.datePosted.getFullYear()}`;
    }
}
</script>

<style scoped>
h2 {
  text-decoration: underline;
}
p.meta {
  font-style: italic;
}
</style>

Then Home.vueuse:

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
       <HelloWorld v-for="blogPost in blogPosts" :post="blogPost" :key="blogPost.title"/>
  </div>
</template>

<script lang="ts">
import {Component, Vue} from'vue-property-decorator';
import HelloWorld, {Post} from'@/components/HelloWorld.vue';//@ is an alias to/src

@Component({
  components: {
    HelloWorld,
  },
})
export default class Home extends Vue {
    private blogPosts: Post[] = [
        {
          title:'My first blogpost ever!',
          body:'Lorem ipsum dolor sit amet.',
          author:'Elke',
          datePosted: new Date(2019, 1, 18),
        },
        {
          title:'Look I am blogging!',
          body:'Hurray for me, this is my second post!',
          author:'Elke',
          datePosted: new Date(2019, 1, 19),
        },
        {
          title:'Another one?!',
          body:'Another one!',
          author:'Elke',
          datePosted: new Date(2019, 1, 20),
        },
      ];
}
</script>

Run the project at this time

This is the simple parent-child component

6. Reference articles

TypeScript — JavaScript with superpowers — Part II

VUE WITH TYPESCRIPT

TypeScript + actual combat of large-scale projects

Python modifier (1)-function modifier "@"

What is the difference between interface and type in Typescript

Author Nuggets Article Collection

If you need to repost it to the official account, just call me to add the whitelist.

  • "True® Full Stack Road" Back-end guide for web front-end development
  • "Vue Practice" A Vue CLI plug-in in 5 minutes
  • "Vue practice" arm your front-end project
  • "Intermediate and advanced front-end interview" JavaScript handwritten code invincible cheats
  • ``Learn from the source code'' Vue question answers that interviewers don't know
  • "Learn from the source code" JS Sao operation in Vue source code
  • "Learn from the source code" thoroughly understand the Vue option Props
  • The correct posture of the "Vue practice" project to upgrade vue-cli3
  • Why can't you understand the JavaScript scope chain?
Reference: https://cloud.tencent.com/developer/article/1488419 TypeScript best practices before Vue 3.0-Cloud + Community-Tencent Cloud