The trend of ts, are you still hanging around vue+ JS? Come on, it’s time for some real technique

From vUE began to get hot up to now, has basically front-end development partners entry-level skills. I believe that after such a long time passed, everyone has long been used to the development model of VUE. So, don’t you want to shine when you compare yourself to others? While the current SUPPORT for TS is not as friendly as those supported by React, NG, etc., vuE2 + is perfectly ok to use VUE + TS for production projects as the community-related toolchain is improved.

I believe many friends have already sharpened their knives and are eager to try. Then how to standardize and systematically use the development mode of VUE + TS in the actual production project of VUE? This article systematically explains how to develop typescript+ Class + annotation style projects in VUE2 +. Graphic details, systematic introduction, focus on pit, let’s use VUE + TS in production projects. Ollie here

✨ get ready to take off

  • Speaking of getting started, let’s start with the basic tools and environment. I used 4+ CLI scaffolding, as shown in the figure:

  • Initialize the project using vue-CLI
# Terminal run
vue create vue-ts-demo
Copy the code

As you can see, after the command is run, let’s select the configuration parameters related to the project, in this case we’ll say Manually select fetures

  • Check the configuration

The most important thing to do here is to check typescript. Let’s do configuration for everything except PWA. Of course, you can also according to your needs, but TS must be checked, note is TS project ~ ~ ~

  • Select the class style syntax

I’m gonna go ahead and select class style and hit enter. If you don’t like the class style, you can choose no, but we recommend that you choose the class style for ts development, elegant. The final class style code will look like this:

Only partial screenshots are shown, and will be explained in detail later

  • Continue selecting the configuration until it is complete

The following are some basic configuration choices, such as CSS preprocessor, code specification, configuration file location, router mode, etc., depending on the team specification or individual style, there is no need to specify this. Finally, wait for the project initialization to complete.

  • run
Enter the project folder
cd vue-ts-demo

# Run the project
npm run serve
Copy the code

After the cli4+ project is initialized, in fact, the dependencies are already installed, and there is no need to NPM I again

Once the project starts, you should see a familiar page

✨ Basic directory explanation

  • The directory where the project was initialized

As you can see from the diagram, the basic directory is basically the same as the JS version of the Vue project. As an additional note, you can see that tsconfig.json has been added to the root directory as the ts configuration file, as well as other configurations that are in the form of separate configuration files, because I’ve checked that the configuration files exist separately. You can also choose to put your projects in the browserList configuration when initializing them.

Also, note that all previous.js files are now.ts files.

  • Basic vUE component demonstration
<template> <div class="hello"> </div> </template> <script lang="ts"> import { Component, Prop, Vue } from 'vue-property-decorator'; @Component export default class HelloWorld extends Vue {// Declare props @prop () private MSG! : string; // Declare a variable private count: Private addCount() {this.count++}} </script> <style scoped lang="less"> </style>Copy the code

As you can see, the vUE component’s three major components are the same three, template, script, style. Note that script tags need to be marked with lang=”ts” to indicate that the current syntax is TS.

In addition, the basic usage of components has become annotation +class style. We imported what we needed from the vue-property-decorator, and finally exported an inherited class, which can also be anonymous (if you want to be a little lazy) :

@Component
export default class extends Vue {
    // ...
}
Copy the code
  • Specifies the class component
import {
  Component, Vue, Prop, Watch, Emit,
} from 'vue-property-decorator';

@Component
export default class Header extends Vue {
  // Attributes in data are written directly as instance attributes
  // The default is public
  nums = 0;
  // You can also define read-only
  readonly msg2 = 'this is readonly data'
  // Define private attributes
  private msg3 = 'this is a private data'
  // Define a private, read-only data
  private readonly msg4 = 'this is a private and readonly data'
  
  
  // @Prop定义props
  // Braces define the parameters of prop, such as default, type, required, etc
  @Prop({ default: 'this is title'}) title! : string; @Prop({default: true }) fixed: boolean | undefined;

  // Write the computed property in the getter
  get wrapClass() {
    return ['head', {
      'is-fixed': this.fixed,
    }];
  }

  // Observe the properties
  // You can use the second parameter to set immediate and deep
  @Watch('nums', { immediate: true.deep: true })
  handleNumsChange(n: number, o: number) {
    console.log(n, o);
  }

  // Define the emit event. The parameter string represents the event name to be distributed
  // If not, use the method name as the distribution event name, and the hyphen is automatically transferred
  // Note that this does not seem to give more than one parameter,
  $emit = this.$emit = this.$emit = this.$emit = this.$emit = this.$emit = this.$emit
  @Emit('add')
  emitAdd(n: number) {
    return n;
  }
  @Emit('reduce')
  emitReduce() {
    return (this.nums, 123.321);
  }

  // All method names are written directly as methods of the class
  private handleClickAdd() {
    this.nums += 1;
    this.emitAdd(this.nums);
  }
  private handleClickReduce() {
    this.nums -= 1;
    this.emitReduce(); }}Copy the code

The above component, in the comments, details the basic common syntax of TS components. With this syntax, basic page writing and data rendering should be fine. For class components, the project actually uses the vue-class-Component and vue-property-decorator libraries. For more basic syntax, you still need to consult the documentation.

The relationship between the two libraries is explained here. Vue-class-component is an officially maintained library for VUE to support the class style. Vue-property-decorator is also based on Vue-class-Component, but supports annotation syntax on top of it. This makes our code syntax more concise and easy to reuse. As you can see from our project, we actually end up using the vue-property-decorator syntax.

  • Add a command for NPM to consult documents

Most of the time, if we want to open the document address or source address of a library quickly (and don’t bother to look it up), we can do it quickly with the NPM command, just run the terminal:

# Check the documentNPM Docs Library nameFor example, query the document address of vue-class-component
npm docs vue-class-component

# check the source addressNPM Repo library nameCopy the code

So how does this work?

These diagrams are actually developed with parameters specified in their package.json files. If you are interested in NPM, you can continue to browse the NPM documentation to learn more about NPM.

Guide to routing pits under TS

Now that we’ve talked about the basic use of vue single files, we’re ready to talk about routing. In TS mode, there are some things that need to be explained about routing, otherwise you will be confused in the project

  • Basic route usage

As shown in the figure, the basic routing mode has been configured when the project is initialized, but the type ts has been added to the original basis.

Note that base is set to process.env.base_URL. Where did this come from? Actually read the information in the configuration file. Cli4 + initialization of the project is to support the configuration file. You can create several profiles in the root directory, depending on the environment:

Env, followed by the environment type. For example, here is a brief demonstration of the local development environment, online dev environment, and online production environment configuration files. In the configuration file, we can perform different configurations according to different environments. For example:

For example, in the above route base configuration, the API address of different environments will be configured here most of the time. Friend here may have a small question, why is configured with the local environment is configured with a dev environment, if your local development of cross-domain, need you to handle, and then end you cross domain here alone handling API to configure a local environment, and then do some agent, and then in the online dev not to deal with the issue of cross-domain. Depending on your needs.

Let me add a little more about what to do if the front end processes across domains. Create a vue.config.js file in the root directory, this is the method of custom webpack script configuration in CLI4, you can browse the CLI document, there are detailed instructions. Here’s how to configure it:

// Take me for example
module.exports = {
    devServer: {
    proxy: {
      '/hermes/api/v1/': {
        // The domain address to be proxied
        // that is, when '/hermes/ API /v1/' is encountered, broker the request to 'http:xxxx.com'
        target: 'http:xxxx.com'.changeOrigin: true
      },
      '/bms/api/v1/': {
        target: ' 'http:xxxx.com', changeOrigin: true } } }, }Copy the code

If ‘/hermes/ API /v1/’ is used, proxy will be used to proxy requests to ‘http:xxxx.com’.

At this point, we need to show you how to write the API:

// For example, this is an account.ts module // introduce the domain name prefix in our environment variable // in local development, it must be the domain name prefix we set above // In this case, Const {VUE_APP_API_KB} = process.env; export interface ILogin { account: string; password: string; } export const login = (data: ILogin): Promise<object> => {data.password = md5(data.password); return request({ url: `${VUE_APP_API_KB}login`, method: 'post', data, }); };Copy the code
  • Registration of route hooks

To use routing hooks, it is important to register them, otherwise they will not be available. So how do you sign up? Let’s create a new class-component-hooks. Ts file in the router folder:

import Component from 'vue-class-component';
// or
// import { Component } from 'vue-property-decorator';

// Register the router hooks with their names
Component.registerHooks([
  'beforeRouteEnter'.'beforeRouteLeave'.'beforeRouteUpdate',]);Copy the code

Then in the mian. Ts file, the import is made at the very top, ensuring that it is executed before all components are introduced. This is important!!

// main.ts import './router/class-component-hooks';Copy the code
  • Local route guard parameter type

Back to routing. One extra thing to note about routing is the use of route guards, which you should be careful not to report errors.

First, let’s look at the parameters of the route guard in the page:

// Introduce the annotations plugin to expose the relevant content
import { Vue, Component } from 'vue-property-decorator';
// Import the Route object from the Route
import { Route } from 'vue-router';


@Component({})
export default class App extends Vue {
    // To define the parameter type, use Route,
    // Next is a Function, so you can use the Function object directly,
    // 或者是next: () => void
    beforeRouteEnter(to: Route, from: Route, next: Function){ next(); }}Copy the code

As you can see, when we use a Route hook like beforeRouteEnter, we need to define the type of the parameter. Here we introduce the Route type from vue-router as the parameter type for from and to. Because the ts function arguments need to be typed. The next argument, if it’s easy, can just be a Function.

✨ TS how to choose Vuex and its pit point

Speaking of vuex, this is one of the main points of vue’s project. I believe that you are very comfortable with how to use vuex for js versions of projects. Here’s how to use VUex in the TS version.

Here, I’m introducing Vuex-module-decorators, including the library I also use in my project. In addition, there is also a VUex-class, both of which can help us better use VUEX in TS. But why I chose and recommend vuex-module-decorators will be explained later. Here’s how to use the library:

  • Install vuex – module – decorators
# installation vuex - module - decorators
Support annotated development of store content
cnpm i vuex-module-decorators -S
Copy the code

Let me take a look at what our default store was:

As you can see, the store structure is basically the same as the normal store structure that our project initializes, except that index.js becomes index.ts.

Let’s organize the files in a basic way. Projects are usually divided into modules, unless the project is really small. The following figure should be a familiar code organization structure:

Normally, we would divide the modules into modules, with index.ts as the exit and constant.ts as the constant file. Mutation types. Ts is the type management document of mutations. I’m not going to talk about store.ts for the moment, because this is the structure of the file that we’re going to change later;

Next, let’s go over how each document should be written and organized, one by one.

  • Store /index. Ts, sotre files are uniformly exposed for export
// store/index.ts export { default as AccountModule } from './modules/account'; export { default as DownloadModule } from './modules/download'; export { default as DownlableModule } from './modules/kbAnalyse'; / /...Copy the code

As shown above, it is our index.ts file. What do we do in this file? We import the modules from our moudles folder and expose them in a unified way. If you’re not sure why the syntax is written this way, click here to see how ES6 imports and exports are written together.

So why are we doing this? Why unify imports and exports? Just for ease of use, let’s take a look at how the data and methods in the Store are used on the page.

  • How is store data used in a component

We know that in normal mode, the vUE component uses the store as follows:

Import {mapState, mapGetters, mapActions} from 'vuex' export default {computed: {... mapGetters('analyse', [ 'someGetterName' ]) }, methods: { ... mapActions('analyse', [ 'customResetState' ]), } }Copy the code

Let’s look at how vuex data and methods are used after vuex-module-decorators are introduced:

Import {Component, Vue, Ref} from 'vue-property-decorator'; Import {AccountModule} from '@/store/index'; @Component({ name: 'Login', }) export default class extends Vue {// Omit the other code /** * Send a login request */ private Async submitLogin() {if (this.isLoading) return; try { this.isLoading = true; const params = clone(this.formData); // We call the action await accountModule.login (params) in the store directly from the module; const { redirect } = this.$route.query; this.$router.replace(redirect ? decodeURIComponent(redirect) : '/'); } catch (error) { this.$Notice.error({ title: error }); } finally { this.isLoading = false; } // Omit other code}Copy the code

The above illustrates the basic invocation of an action. Other states, getters, and so on are the same, directly calling the corresponding content in the module. B: So, it’s quite convenient. More ts type derived blessings, very convenient.

  • How do I define a store module

Now that we’ve seen how store/index.ts is used in components, let’s look at how to define a module. Speaking of developing modules, we can see that we only import and export modules in index.ts, but we haven’t actually instantiated our Store instance. Because it was instantiated in index, we used index.ts to import and export, so the instantiation part is missing.

The reason for importing and exporting in index.ts is to make it easier for component users to import the store module. According to node.js file search rules, we just need to write ‘@/store’. With that said, it’s time to check out our store/store.ts:

// store/store.ts

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({});
Copy the code

You can see that our store.ts is actually being used to instantiate the store. Note that we’re not simplifying the demo here, and we’re actually doing nothing when we instantiate the Store, because what we want to do is dynamically register the Store module, which is supported by the Store. Let’s look at how to dynamically register the Store module.

  • Dynamically register the Store module

Let’s look at the basic format we use to write store modules:

import store from '@/store/store'; import { VuexModule, Module, Mutation, Action, getModule, } from 'vuex-module-decorators'; @Module({ dynamic: true, namespaced: true, name: 'account', store,}) class account extends VuexModule {/ / state demonstration, the user token public token = getToken () | | '/ / getter demo, Public get platformGetter(): string[] {return this.userConfig.platform; } @mutation private [types.set_token](token: string) {this.token = token; setToken(token); @action ({commit: types.set_token, rawError: true}) public async login(params: accountApi.ILogin) { const data = await accountApi.login(params); return data; }}Copy the code

Through the above demonstration, we should pay attention to a few points:

We need to define a class that inherits VuexModule as the exported Module

Second, we attribute the state object through the instance attribute, attribute the getter attribute of vex through the get attribute of the class, and develop the Mutation and Action for the Mutation and Action

3. We dynamically register the Module in @module ({}), which requires us to manually pass in the store and specify dynamic: true; Namespaced is used to enable the namespace and specify the module name.

[types.set_token]() {} is an es6 attribute name expression that uses the expression as the key of the attribute. If you don’t know, you can click on the attribute name expression to make up for it.

There are two things to note here. First, if you specify who the commit is in the action argument, you can return the data directly in the action method, which would also call the above commit method

@Action({ commit: types.SET_TOKEN}) public async login() { const data = await accountApi.login(); return data; } public async login() {const data = await accountapi.login (); This.context.com MIT (types.set_token, data)}Copy the code

The normal call to COMMIT is passed through this. Context, but if you only need one, it can be abbreviated. If more than one commit is required, then only an honest commit is required.

Another thing to note about action is the catch of errors, as shown below:

@Action({ commit: types.SET_TOKEN, rawError: true })
Copy the code

The issue rawError

RawError is enabled globally, at the end of the document

If you do not set rawError: true here, you will have problems with active throwing. It’s important to pay attention to this.

Sixth, the last point, as for how to get other modules state, getter, action, etc., directly print some related parameters and carrier objects to know, not to say.

  • Mutation-types centrally manages all Muation

There is not much to say about this, as usual with the project, all mutation names are managed centrally. But if the name is more and more, in fact, it is not good management, naming easy conflict, if really many, it is recommended to divide module.

export const SET_TOKEN = 'SET_TOKEN';
export const RESET_TOKEN = 'RESET_TOKEN';
export const SET_USER_INFO = 'SET_USER_INFO';
export const SET_USER_CONFIG = 'SET_USER_CONFIG';
export const LOGOUT = 'LOGOUT';
Copy the code
  • Some instructions for TS in the store module

We will define the state in the store, and after introducing TS, we can actually require our Module classes to implement the good types we define:

// store/module/account.ts // Omit... interface IState { token: string | undefined; userInfo: IUserInfo; userConfig: IConfig; } @Module({ dynamic: true, namespaced: true, name: 'account', store, }) class Account extends VuexModule implements IState {/ / the user token public token = getToken () | | '/ public/user information UserInfo: IUserInfo = getDefaultUserInfo() public userConfig: IConfig = getUserConfig()} Omit the otherCopy the code

As you can see, we require that our Account, after inheriting VuexModule, must implement the type IState we defined. That is, our class must have the type contained in IState, or ts will give me an error. Also, these instances must be of the type we defined earlier. This will make the code more rigorous and more cooperative, which is also the advantage of TS.

✨ vuex-module- Decorators vs. vuex-class

I also mentioned earlier why VUex-module-decorators were chosen to compare the two libraries.

  • First of all, in terms of usage, both are of various ages.

Vuex-moder-decorators work with the syntax of annotations (decorators) when defining a module, by directly introducing the corresponding Module when used, and then directly using the properties or methods on the Module.

Vuex-class is when you define a module, just like a normal project, except that you need to add types to the variables. But it is used through annotations (decorator syntax), as shown below:

As you can see, vuex-module-decorators are more elegant when defining modules, and vuex-class is better when used.

  • A combination of the two?

The combination of the two is also completely possible, the author has also tried, can indeed. Isn’t that perfect? So why give up the Vuex-class? The reason for this is that when vuex-class is used, the type support for TS is partially lost and only any can be specified. This makes us lose the significance of introducing TS to some extent, so the author chooses another one after it. Specific examples, now also do not want to demo, perhaps there is a better way to deal with, and the author has not found it, there are clear partners are welcome to point out.

✨ Eslint does not recognize “alias” handling

  • Install the ESLint plug-in
# If eslint-plugin-import is already installed, there is no need to repeat the installation
cnpm install eslint-plugin-import  eslint-import-resolver-alias --save-dev
Copy the code
  • Add configuration to.eslintrc.js file
/ /... Omit the other

settings: {
    'import/resolver': {
      alias: {
        map: [[The '@'.'./src']],extensions: ['.ts'.'.js'.'.jsx'.'.json']}}}/ /... Omit the other
Copy the code

Eslint – import – resolver – alias documentation

✨ Enhanced type to handle global mount issues

// If you want to mount a method on Vue. Prototype, for example: Vue.prototype.$success = (msg = '', config = {}) => { Vue.prototype.$Notice.success({ title: msg, ... noticeDefaultConfig, ... config, }); }; $success does not exist. $success does not exist. // SRC /shims-vue.d.ts import vue from 'vue' import VueRouter, {Route} from 'vue-router' // Declare module '*. Vue '{import vue from 'vue'; export default Vue; Declare module 'vue/types/vue' {interface vue {$router: VueRouter $route: route $success: Function } }Copy the code

✨ What can I do if the third-party library does not support TS

  • Go to @types
// The community maintains the @types/ XXX library // the main js library, @types/ basically maintains its TS version // so go to NPM to search. For example, lodash@types /lodash // install it directly if there is one, and be sure to install the local development dependency CNPM i@types /lodash -dCopy the code

What about libraries that are not supported by @types?

  • Add YOUR own TS support to the library, limited to space, more content can be viewed in the TS documentation. In the future, if possible, I will consider writing the contents of TS, and then talk about it in detail.

How is ✨ interface organized

Finally, interrace, with more and more interface type declarations, it might be better to manage interfaces centrally as modules.

How is ✨ Components organized

As for the Components folder, which is our common component, I prefer to use the index.ts file to import and export all components uniformly. This way, when importing a component, you don’t have to worry about the actual location of the component, you just need to import it from components/index. Take a look at the Components folder:

For the naming of component files, you can refer to the official website documentation for suggestions, but ultimately it is the team specification. For example, all Base components start with “Base”. For a component that is used only once in a module, start with “The”. Everything is named with a big hump, including component calls. See the specification for this. Take a look at how the index.ts file is organized:

export { default as BaseButton } from './BaseButton/index.vue'; export { default as BaseSideContainer } from './BaseSideContainer/index.vue'; export { default as BaseNofify } from './baseNotify/index.vue'; / /... Omit the otherCopy the code

Let’s look at the import again:

import { BasePopoverConfirm } from '@/components';
Copy the code

The biggest benefit is that the component folder structure and component location relationships of the Care component are not required externally. Changes in the organizational structure of components within components have no effect on external entry paths at all.

Update (2020-08-13)

✨ handles the type definition of methods mounted by third-party libraries on vue prototypes

  • recommended

The best way to do this is to import all the defined file types directly into SRC /shims-vue.d.ts and it will automatically merge.

import Vue from 'vue'
import 'vue-router/types/vue'
import 'element-ui/types'

declare module '*.vue' {
  import Vue from 'vue';

  export default Vue;
}
Copy the code

As shown in the figure, the element-UI has already merged the types that need to be merged, so we only need to introduce them:

The same is true for a vue-router:

  • The old way of doing

You need to import it manually and merge it into the VUE interface

import Vue from 'vue'
import VueRouter, { Route } from 'vue-router'
import { Message } from 'view-design/types/message.d'

declare module '*.vue' {
  import Vue from 'vue';

  export default Vue;
}

declare module 'vue/types/vue' {
  interface Vue {
    $router: VueRouter
    $route: Route
    $success: Function
    $error: Function
    $Message: Message
  }
}
Copy the code

✨ Dynamically adding a route

For example, vUE uses router-addroute to dynamically register routes.

The only thing to notice is that wildcards match routes. Make sure it’s at the end! Make sure it’s at the end! Make sure it’s at the end!

@Mutation private [types.GENERATE_USER_ROUTES]() { const routes: any = []; (this.userAuths || []).forEach((auth) => { if (auth.children? .length) { auth.children.forEach((item) => { const curRoute = dynamicDicts.find((e) => e.name === item.name); if (curRoute) routes.push(curRoute); }); }}); // Note, add to the last routes. Push (route404); this.userRoutes = routes; }Copy the code

✨ develops global filters as plug-ins

The basic format of the plug-in is not to say, the specific directory structure is not to say. The only thing to emphasize here is the typeof the vm, using typeof Vue. The same goes for other places like plug-ins and so on that need to use vue instances.

import Vue from 'vue'; import dayjs from 'dayjs'; import { isFalsy, Falsey } from 'utility-types'; import { DateFormat } from '@/store/constant'; Export default {install(vm: typeof Vue) {// install(vm: typeof Vue) { string | number | Date | Falsey, format = DateFormat.default) => { if (isFalsy(t)) return '-'; return dayjs(t).format(format); }); }}; ### ✨ ts share less/ SCSS and other variables. For example, it is very common in the project, we need to use our theme colors in some components, and many times these colors are defined by less, sass and other variables. This requires us to deal with the need to share less and sass variables in TS (with the exception of css-in-JS, which can be a bit uncomfortable). Of course, we could not use the less variable in js, but then we would need to define it again in the steady on, which is unnecessary and redundant. */ @primary-color: #007EE6; */ @primary-color: #007EE6; /* @primary-bg: #f0f2f5; / * * * : export instructions are provided on the webpack for Shared variables in js and less functions * https://mattferderer.com/use-sass-variables-in-typescript-and-javascript */ :export { primaryColor: @primary-color; }Copy the code

Note: Export is a webpack way to share variables between JS and CSS processors such as Less/Sass. However, it is also important to note that in ts, if you use it directly, you will still get untyped errors, so you need to write a declaration file to the variable file.

  • The declaration file that defines the var.less file

Var.less. D. ts: var.less. D. ts: var.less.

export interface LessVariables {
  primaryColor: string
}

export const variables: LessVariables

export default variables
Copy the code
  • use
Import variables from '@/styles/var.less'; @prop ({type: String, default: variables. PrimaryColor}) hoverColor! : string // can also be used to evaluate attributes, etcCopy the code

✨ interface management

You are advised to create an Interface folder in the SRC directory and divide it by module to manage all interfaces and types. As shown in figure:

👍 👍 👍 finally

I hope that after reading this article, people who have not yet used vUE + TS development can develop and use it in real projects as soon as possible. I believe that with these mastered, the actual project of VUE + TS development should be basically OK. I hope this article is helpful to those who want to use TS, so please remember to add “like”!!

I am your old friend Leng hammers, if you feel like it, welcome to like it!!