preface

Vue3.x is coming, TypeScript refactorings with TypeScirpt will become standard in the Vue community, and out of the anxiety of a programmer, vue 2.6.x is now in a rush.

The vue documentation gives a brief overview of TypeScirpt support, and we use vue Cli3 to generate projects directly

Create a project

❓ Why build a project with Vue Cli3

Official maintenance, subsequent upgrades reduce compatibility issues

Build the project using the following configuration:

  • Babel translates Ts
  • TSLint standardizes TS code, followed by prettier for code unification
  • Vuex and Router are installed by default. The Router uses history mode
  • Use Jest for unit testing
╭ ─ ~ / otherEWokspace ╰ ─ ➤ vue create ts - vuex - demo vue CLI v3.6.3 ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ Update available: 3.9.3 │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘? Please pick a preset: Manually select features ? Check the features neededfor your project: Babel, TS, Router, Vuex, CSS P
re-processors, Linter, Unit

? Use class-style component syntax? Yes

? Use Babel alongside TypeScript for auto-detected polyfills? Yes

? Use history mode for router? (Requires proper server setup for index fallb
ack in production) Yes

? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are suppor
ted by default): Sass/SCSS (with node-sass)

? Pick a linter / formatter config: TSLint

? Pick additional lint features: (Press <space> to select, <a> to toggle all
, <i> to invert selection)Lint on save

? Pick a unit testing solution: Jest

? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In de
dicated config files

? Save this as a preset for future projects? Yes

? Save preset as: ts-vue-demo
Copy the code

Take a look at the hierarchical directory for the new project

╰─ Blames tree-L 2-I Node_Modules. ├─ Readme.md ├─ Babel.config.js ├─ when the panel is up for air › ╰─ Tree-L 2-I Node_Modules ├─ download.config.js ├─ Download.javascripts ├─ Download.config.js ├─ Public │ ├─ Favicon Index.html ├ ─ ─ the SRC │ ├ ─ ─ App. Vue │ ├ ─ ─ assets │ ├ ─ ─ components │ ├ ─ ─ main. Ts │ ├ ─ ─ the router. The ts │ ├ ─ ─ shims - TSX. Which s │ ├ ─ ─ ├─ ├─ exercises - ├─ exercises - └.txt └.txtCopy the code

tsconfig.json

Explain lib, target, and Module

{
  "compilerOptions": {
    "target": "esnext"."module": "esnext"."strict": true."jsx": "preserve".// Enable JSX support
    "importHelpers": true."moduleResolution": "node"."experimentalDecorators": true."esModuleInterop": true."allowSyntheticDefaultImports": true."sourceMap": true."baseUrl": "."."types": [
      "webpack-env"."jest"]."paths": {
      "@ / *": [
        "src/*"]},"lib": [
      "esnext"."dom"."dom.iterable"."scripthost"]},"include": [
    "src/**/*.ts"."src/**/*.tsx"."src/**/*.vue"."tests/**/*.ts"."tests/**/*.tsx"]."exclude": [
    "node_modules"]}Copy the code
  • targetGenerate JS file code style after compiled by TSC
  • moduleThe style of the module generated by TSC after compiling js files
  • lib— Code base supported by the original TS file

Let’s take a look at an example:

// index.ts
export const Greeter = (name: string) = > `Hello ${name}`;
Copy the code
  • "module": "commonjs", "target": "es5"
// index.js
"use strict";

Object.defineProperty(exports, "__esModule", { value: true });

exports.Greeter = function (name) { return "Hello " + name; };
Copy the code
  • "module": "es2015", "target": "es5"
// index.js
export var Greeter = function (name) { return "Hello " + name; };
Copy the code
  • "module": "es2015", "target": "es6"
// index.js
export const Greeter = (name) = > `Hello ${name}`;
Copy the code
  • "module": "commonjs", "target": "es6"
// index.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Greeter = (name) = > `Hello ${name}`;
Copy the code

If lib does not specify a list of libraries injected by default. The default injected library is:

  • Target :ES5: DOM, ES5, ScriptHost

  • Target :ES6: DOM, ES6, DOM.Iterable, ScriptHost

tslint

Similar to ESLint, ts code is checked.

Vscode needs to install the TSLint plugin and add the following configuration to the VSCode user configuration to automatically resolve TS errors when saving.

// settings.json
 "editor.codeActionsOnSave": {
    "source.fixAll.tsLint": true
  }
Copy the code

❗️ Vue CLI3 has tsLint dependencies installed

usePrettier plug-inTo unify and standardize the code style of the project

  • npm i tslint-config-prettier -D
  • Add the tslint.json extends field as follows:
"extends": ["tslint:recommended"."tslint-config-prettier"]
Copy the code

Set the vscode

  • Check thetslintIntegrationTo enable PritTier to support formatting ts files
  • "editor.formatOnSave": trueAutomatic formatting when saving

You can also use Shift + option + f for formatting

Add the.prttierrc file to the root directory (for ts files in the pritTier format vue file, you can’t format them using tsLint rules and need to process them separately to avoid tsLint errors)

{ "singleQuote": true }
Copy the code

shims-vue.d.ts

declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}
Copy the code

Declare that all files ending in. Vue are imported by default and exported by default to identify.vue files in the project.

In main.ts, importing a vue component must end with.vue.

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');

Copy the code

Vue class

vue-property-decorator

Write a Todolist component that introduces vue-property-decorator, using element-UI for easy page building

Element-ui is developed using TS and has a.d.ts declaration file by default

npm i element-ui
Copy the code
// main.ts

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI);
Copy the code

Create todolist. vue from/SRC /compenents/ :

<template>
  <div class="todo_list">
    <el-card class="box-card">
      <div slot="header">
        <el-row :gutter="18">
          <el-col :span="18">
            <el-input
              id='todo'
              v-model="todo"
              placeholder="Please enter the content"
            ></el-input>
          </el-col>
          <el-col :span="2">
            <el-button
              id="add"
              type="primary"
              icon="el-icon-circle-plus-outline"
              @click="addItem"
            >add</el-button>
          </el-col>

        </el-row>

      </div>
      <div
        v-for="(item,index) in todoList"
        :key="index"
        class="text item"
        @click="removeItem(index)"
      >{{ item }}</div>
    </el-card>
    <label
      class="text"
      style="margin-top:20px"
    >{{todoLength}} records</label>
  </div>
</template>

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

@Component
export default class HelloWorld extends Vue {
  public todo: string = ' ';

  @Prop({ default: () => [] }) private readonlytodoList! : string[]; get todoLength(): number {return this.todoList.length;
  }

  @Emit()
  private addItem(): string {
    return `${this.todo}`;
  }

  @Emit('removeItem')
  private removeItem(index: number): number {
    returnindex; } } </script> <! -- Add"scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.todo_list {
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
  .box-card {
    width: 480px;
  }

  .text {
    font-size: 14px;
    text-align: left;
  }

  .item {
    margin-bottom: 18px;
  }
}
</style>

Copy the code

The usage of TS code points out the following:

  • Prop proposes to writexxx! : typeIs, otherwise, written asxxx : type | undefined
  • The @emit event name is the modified function name. However, if the function name is camelCase, the registered function name must be kebab-case
  • The @emit argument is decorated by the function return value

Modify home.vue as follows:

<template>
  <div class="home">
    <todoList
      :todoList="[]"
      @add-item="addTodoList"
      @removeItem="addTodoLisItem"
    />
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import todoList from '@/components/todoList.vue'; // @ is an alias to /src
import { State, Getter, Action } from 'vuex-class';

@Component({
  components: {
    todoList
  }
})
export default class Home extends Vue {
 
  public addTodoList(val: string) {
    console.log(val);
   
  }

  private created() {
    console.log('i add life cycle funciton -- created');
  }

  private addTodoLisItem(index: number) {
    console.log(index);
  }
}
</script>

Copy the code

Vuex

The way to write vuex in TS starts with vuex-class, which is also recommended in the official vue-property-decorator.

npm i vuex-class
Copy the code

Create a store folder in the SRC folder and create index.ts and todolist. ts in store

// index.ts

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

import todolist from './todoList';

Vue.use(Vuex);

export default new Vuex.Store({
  modules: { todolist }
});

Copy the code
// todoList.ts

import { Commit, Dispatch, GetterTree, ActionTree, MutationTree } from 'vuex';

const ADD_TODOLIST = 'ADD_TODOLIST';
const REMOVE_ITEM = 'REMOVE_ITEM';

export interface RootState {
  version: string;
}

interface Payload {
  [propName: string] :any;
}

interface TodoListType {
  todoList: string[];
}

interface Context {
  commit: Commit;
  dispatch: Dispatch;
}

const dataSource: TodoListType = {
  todoList: []
};

const getters: GetterTree<TodoListType, RootState> = {
  getTodoList(state: TodoListType): string[] {
    returnstate.todoList; }};const mutations: MutationTree<TodoListType> = {
  ADD_TODOLIST: (state: TodoListType, item: string) = > {
    console.log(item);
    state.todoList.push(item);
  },
  REMOVE_ITEM: (state: TodoListType, removeIndex: number) = > {
    state.todoList = state.todoList.filter((item: string, index: number) = > {
      return removeIndex !== index;
    });
  }
};

const actions: ActionTree<TodoListType, RootState> = {
  addList: async ({ commit }: Context, item: string) = > {await Promise.resolve(
      setTimeout((a)= > {
        commit(ADD_TODOLIST, item);
      }, 100)); }, removeItem:async ({ commit }: Context, { index }: Payload) => {
    await Promise.resolve(
      setTimeout((a)= > {
        commit(REMOVE_ITEM, index);
      }, 100)); }};export default {
  namespaced: true,
  state: dataSource,
  getters,
  mutations,
  actions
};


Copy the code

Delete store.ts at the same level as main.ts

The following points to note about Todolist.ts:

  • For the type of the response of getters, mutations and Actions, you can use the command + left click to enter the declaration file to view, or you can not specify type, but it is recommended to write
  • Tslint reports an error for Payload deconstruction. You can add a type declaration for the Payload
interface Payload {
  [propName: string]: any;
}

Copy the code
  • The dataSource in this code is intended to be state, but cannot be named with state, as tsLint will conflict with the parameter state

/views/ home.vue

<template>
  <div class="home">
    <todoList
      :todoList="todoList"
      @add-item="addTodoList"
      @removeItem="removelistItem"
    />
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import todoList from '@/components/todoList.vue'; // @ is an alias to /src
import { State, Getter, Action } from 'vuex-class';

const namespace = { namespace: 'todolist' };
@Component({
  components: {
    todoList
  }
})
exportdefault class Home extends Vue { // @State(state => state.todolist.todoList) private todoList! : string[]; @State('todoList', namespace) public todoList! : string[]; @Action('addList', namespace) private addList! : (val: string) => void; @Action('removeItem', namespace) private removeItem! : ( payload: object ) => void; // @Action('todolist/removeItem') public removeItem! : (index: number) => void; public addTodoList(val: string) { console.log('val', val);
    if (val) {
      this.addList(val);
    }
  }

  private created() {
    console.log('i add life cycle funciton -- created');
  }

  private removelistItem(index: number) {
    console.log(index);
    this.removeItem({ index });
  }
}

</script>


Copy the code

There are a few things to note about the invocation of vuex-class

  • All the parameter declarations of the decorator can be viewed using the Command + left click. The above code is written in the most elegant way
  • The declaration of a function in @action that must take the same parameters as the method

All code ends here, use NPM run serve to view the application, keep the original routes file, keep the application robust.

Jest

This project uses vue-test-utils to write, combined with a variety of situations, obtained the following code:

Create todolist.spec. ts in SRC /tests/unit

// todoList.spec.ts
import { mount, shallowMount, createLocalVue } from '@vue/test-utils';

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

import ElementUI from 'element-ui';

import todoList from '@/components/todoList.vue';
import home from '@/views/Home.vue';

const localVue = createLocalVue();

localVue.use(ElementUI);
localVue.use(Vuex);

describe('todoList.vue'.(a)= > {
  let actions: any;
  let store: any;

  beforeEach((a)= > {
    actions = { addList: jest.fn(), removeItem: jest.fn() };
    const todolist = {
      namespaced: true,
      state: { todoList: [] },
      actions
    };
    store = new Vuex.Store({
      modules: {
        todolist
      }
    });
  });

  it('renders props.msg when passed'.(a)= > {
    const father = mount(home, { store, localVue });
    // const child = shallowMount(todoList, { localVue }) as any;
    const child = father.find(todoList) as any;
    child.vm.todo = 'todo 1';
    child.find('#add').trigger('click');
    expect(child.emitted()['add-item']).toBeTruthy();

    expect(actions.addList).toHaveBeenCalled();
  });
});

Copy the code
  • Used when using a third-party librarycreateLocalVueFor processing
  • If the vuex simulation is usefulmodulesYou have to specifynamespaced: true,
  • If you want to use find on a child component after retrieving it from the parent componentmountPackage, cannot use shallowMount

To view the test result, call NPM run test:unit on the CLI

The final project directory structure is as follows

╰─ Blames tree-L 4-I Node. ├─ Readme.md ├─ Babel.config.js ├─ the panel of TS-vex-demo › ╰─ Tree-L 4-I Node.├ ─ The panel of TS-Vex-Demo ├─ download.config.js ├─ Download.javascripts ├─ Download.config.js ├─ Public │ ├─ Favicon Index.html ├ ─ ─ the SRC │ ├ ─ ─ App. Vue │ ├ ─ ─ assets │ │ └ ─ ─ logo. The PNG │ ├ ─ ─ components │ │ └ ─ ─ todoList. Vue │ ├ ─ ─ main. Ts │ ├ ─ ─ The router. Ts │ ├ ─ ─ shims - TSX. Which s │ ├ ─ ─ shims - vue. Which s │ ├ ─ ─ store │ │ ├ ─ ─ index. The ts │ │ └ ─ ─ todoList. Ts │ └ ─ ─ views │ ├ ─ ─ ├─ ├─ unclass.txt └─ unclass.txt └─ unclass.txt └─ unclass.txtCopy the code

Write in the last

  1. This article introduces a simple example of building a TS-Vue application. There are some considerations for robustness and extensibility of the framework, such as webpack configuration, adaptive testing, differentiation of production environments, axois encapsulation, and so on.
  2. There are many vUE features that are not compatible with the vUE + TS recipe, such as the use of this.refs and other features such as vue-property-decorator.
  3. Due to the limited introduction of TS in the official documentation, there must be some deficiencies in the above code. I hope you can correct them.

The source code

Github.com/EricLLLLLL/…