Although there are many articles about TS + Vue scaffolding on the Internet, they often have some shortcomings, which often make it difficult for new people to proceed. After summary, I explored a scaffold in my Github warehouse. There is an example of component and interface configuration, which is also completed by referring to the information of various leaders. Here we explore the scaffolding process and some of the best practices of current code writing.

The project address is typescript-vue

At the beginning of the project

In VUe-CLI 3.0, we have full support for TS, so we don’t have to make a bunch of magic changes to webpack.config.js according to the TS-loader instructions. Standing on the shoulders of giants is king. Therefore, the first step is to install VUE-CLI 3.0 on the computer to ensure the construction of TS-VUE framework.

Installation method:

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

vue --version
Copy the code

Don’t install the 2.x version. See the documentation for details. If you don’t meet the requirements for node versions, you need to upgrade ahead of time.

To create a basic TS-Vue framework (you can use GUI, but I’m comfortable with CLI):

#Choose to manually configure yourself
? Please pick a preset: Manually select features
#Select the preprocessors and tools required for this project
? Check the features needed for your project: Babel, TS, PWA, Router, Vuex, CSS Pre-processors, Linter
#Select whether you want to write components in a class inheritance style
? Use class-style component syntax? Yes
#Babel goes with TS
? Use Babel alongside TypeScript for auto-detected polyfills? Yes
#Select the CSS preprocessor. Here, I choose Less
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): LESS
#Select TSLint as the code format inspector
? Pick a linter / formatter config: TSLint
#Choose to fix the code format when commit and check the format when save
? Pick additional lint features: Lint on save, lint and fix on commit
#Choose to save the configuration of each tool as a separate file in the project directory for easy maintenance
? Where do you profer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files
#There is no need to save the content as a template
? Save this as a preset for future projects? No
#Use the tool to install these packages. The options are NPM and YARN
? Pickthe package manager to use when installing dependecies: YARN
Copy the code

Then it’s time to wait for the installation, and after a long time, if everything goes well, the initial scaffolding is completed. The project directory is as follows:

├─ public // Static page ├─ SRC // Home Directory ├─ Assets // Static Resources ├─ Components // Views // Page ├─ app.vue // page Main import ├─ └.ts // Footware Main import ├─ RegisterServicework. ts // PWA Config ├─ Router.ts // Route ├─ shims-TSx.d.ts // Related TSX module To Import ├─ Shims - vue. Which s / / vue modules into └ ─ ─ store. The ts/configuration/vuex ├ ─ ─ the postcssrc. Js/configuration/postcss ├ ─ ─ package. The json / / dependence ├ ─ ─ Json // ts configure └─ tslint.json // tslint configureCopy the code

Structure transformation

In order to better meet the requirements of business development, the structure was modified by referring to the sharing of a certain tycoon. The completed structure is as follows:

├─ public // Static page ├─ scripts // Related Scripts To Configure ├─ SRC // Home Directory ├─ Assets // Static Resources ├── filters // Filter ├─ lib // Global Plugin ├─ router // Route Config ├── Style // Style ├── types // Route Config ├── // Route Config ├── // Route Config ├── // Route Config ├── // Route Config ├── // Route Config ├── // ├── views // Page ├─ app.vue // page ├─ main.ts // Page ├─ registerServicework.ts // PWA Config ├── .editorConfig // Edit Related Configuration ├─.npmrc // NPM source Config ├─.postcss.config.js // PostCSS Config ├── babel.config.js // Preset Record ├─ └─ └─ └ └ └─ tslint.json // tslint ├─ vue.config.js package.json // Rely on ├─ tsconfig. md // Project README ├─ tsconfig.json // tsLint Configure ├─ vue / / webpack configurationCopy the code

The above project structure of each module split more detailed, easy to iteration and maintenance of the project, at the same time convenient sub-module development needs, through the views folder to maintain their own page logic, common methods and routing configuration to a separate folder module to be responsible for, I believe that experienced partners understand ~

Before introducing the specific transformation, I will introduce several concepts, mainly for those who are not familiar with TS:

  • Put all of the items.d.tsThe files are put into the Types folder for unified maintenance.d.tsWhat are the files for
  • How to maketsIdentify the.vueWhat about components with suffixes? Writing components in pure JS is not subject to this limitation, but using single-file components directly requires some configuration

As far as I can see,.d.ts files are used to tell typescript which files can be parsed by TS or the interface structure of some imported modules. For ease of understanding, there are two examples of ajax.d.ts and Shims-qs.d. ts configuration files. The shims-vue. D. Ts file is used for TypeScript recognition of. Vue files. Ts does not support importing vue files by default, this file tells Ts to import. The document comes with the scaffolding, so we don’t need to write it ourselves. This file is mainly for you to use the JSX syntax in TS, if you need to use the vue render function to use JSX in the render function to use ts static type hints. The ajax. D. ts file is used for the format of the res interface returned by the interface request. It is defined globally in this file and can be used directly by (res: ajax.ajaxResponse). Theory in ts through the introduction of the import module to specify its data structures, or ts – lint will be an error, but most commonly used modules have @ types of writing, the concrete is introduced in the following, just for now. Which have a conceptual understanding of s can, let’s go on.

Route management: Router /index.ts is configured with lazy route loading, but this section is similar to ordinary JS. For details, refer to the relevant files

Vuex modularization management: This is about how vuex can be better used in projects with typescript additions, so there’s a lot to be said. An example store structure looks like this:

├ ─ ─ home / / home directory ├ ─ ─ index. The ts / / vuex state getters mutations action management ├ ─ ─ interface. The ts / / interface management └ ─ ─ index. The ts / / vuex main entranceCopy the code

A folder under Store corresponds to a module, and each module has an interface for interface management, which is also the model of standardized modules. This new interface declaration file is used to specify the State interface type in the current module, so as to clarify what variables are in the State of the module. You can figure out the writing method and organization skills of the ts type interface definition from this file.

export interface HomeContent {
  name: string, m1? : boolean }export interface State {
  count: number,
  message: string, test1? : HomeContent[]// test1? : Array
      
}
Copy the code

You can then write the corresponding module contents under home/index.ts:

import request from '@/utils/api'
import { State } from './interface'
import { Commit } from 'vuex'

// The requested data needs to define an interface to constrain
interface GetTodayWeatherParam {
  city: string
}

const state: State = {
  count: 0.message: ' '.test1: [],}const getters = {
  count: (state: State) = > state.count,
  message: (state: State) = > state.message,
}

const mutations = {
  INCREMENT(state: State, num: number) {
    state.count += num
  },
  DECREMENT(state: State, num: number) {
    state.count -= num
  },
  MESSAGE(state: State, payload: any) {
    state.message = payload.message
  },
}

const actions = {
  // Request. get returns a promise, encapsulated in the api.ts file in the repository
  getTodayWeather(context: { commit: Commit }, params: GetTodayWeatherParam) {
    return request.get('/weather_mini', { params })
  },
}

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions,
}
Copy the code

Above module more complete said in a typeScript state | getters | mutations | actions of writing, topic and meet you. I’m going to use the home.vue file to complete the core of how to gracefully write Vue components in typescript.

Vue component TS writing exploration

After looking through various sources, the following two plug-ins, VUE-property-decorator and vuex-class, are needed to assist in writing VUE components

Extend () extends Vue extends @components + class XXX extends Vue () extends Vue extends @components + class XXX extends Vue () extends Vue extends @components + class XXX extends Vue I prefer the latter, on a case-by-case basis, but it’s not as if the two components can’t be mixed, depending on your preference. The following is based on vue-property-decorator.

The use of the vue – property – the decorator

Vue-property-decorators rely entirely on the official library vue-class-Component and can be used without worrying about maintenance issues. It contains eight decorators to solve the write problem.

  • @EmitSpecify the event emit, which can be used either directly or by specifying the event emitthis.$emit()
  • @InjectSpecifying dependency injection
  • @MixinsMixins injection
  • @ModelSpecify model(less)
  • @PropSpecify the Prop
  • @ProvideSpecify Provide(less)
  • @WatchSpecify the Watch
  • @ComponentComponent modifier

Another example of usage with mixins and external components: foomixin. ts:

import { Vue, Component } from 'vue-property-decorator'
@component
export default class FooMixin extends Vue {
  mixinValue = 'Hello'
}
Copy the code

Examples of component writing:

import { Vue, Component, mixins, Prop, Watch } from 'vue-property-decorator'
import FooMixin from './fooMixin'
import Hello from './Hello.vue'

interface Person {
  name: string,
  age: number
}

@Component({
  componsnts: {
    Hello
  }
})
export class MyComp extends mixins(FooMixin) { // Multiple mixins can be passed in
  @Prop({ default: 0 })
  propA: number

  @Watch('foo')
  onChildChanged (val: string, oldVal: string) {}
  @Watch('bar', { immediate: true.deep: true })
  onPersonChanged (val: Person, oldVal: Person) {}

  created () {
    console.log(this.mixinValue) // -> Hello
  },
   // dynamic component this.$refs.helloComponent.sayHello()$refs! : {helloComponent: Hello
  }
}
Copy the code

Vuex – the use of the class

Provides four modifiers and a namespace that facilitates module indexing to solve Vuex usage problems

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

Examples of the use of official warehouses are as follows:

import Vue from 'vue'
import Component from 'vue-class-component'
import {
  State,
  Getter,
  Action,
  Mutation,
  namespace
} from 'vuex-class'

const someModule = namespace('path/to/module')

@Component
export class MyComp extends Vue {
  @State('foo') stateFoo
  @State(state= > state.bar) stateBar
  @Getter('foo') getterFoo
  @Action('foo') actionFoo
  @Mutation('foo') mutationFoo
  @someModule.Getter('foo') moduleGetterFoo

  // If the argument is omitted, use the property name
  // for each state/getter/action/mutation type
  @State foo
  @Getter bar
  @Action baz
  @Mutation qux

  created () {
    this.stateFoo // -> store.state.foo
    this.stateBar // -> store.state.bar
    this.getterFoo // -> store.getters.foo
    this.actionFoo({ value: true }) // -> store.dispatch('foo', { value: true })
    this.mutationFoo({ value: true }) // -> store.commit('foo', { value: true })
    this.moduleGetterFoo // -> store.getters['path/to/module/foo']}}Copy the code

There are a few points to note in advance of the overall example above:

  • With TS, many data structures at the interface level can be defined in advance, without using any, which is convenient for troubleshooting
  • For the first time, you might get a bunch of reportslintCan be studied carefullyts-configandts-lintTo resolve details through configuration
  • $refsType assertion is required to use the DOM method on it, as discussed below, so don’t worry
  • definepropsUse an exclamation mark to indicate a non-empty assertion, otherwise an error would be maddening.

The home.vue component is written as follows:

import { Component, Prop, Watch, Vue } from 'vue-property-decorator'
import { State, Getter, Mutation, Action, namespace } from 'vuex-class'

const homeModule = namespace('home')
interface WeatherContent {
  low: string,
  high: string,
  type: string
}

@Component
export default class Home extends Vue {
  // Attributes in the original data can be written directly
  public city: string = 'handan'
  public content: WeatherContent = {
    low: ' '.high: ' '.type: ' ',
  }

  @Prop({ default: 0}) public propA! : number @homeModule.State('message') public message! : string @homeModule.Getter('count') public count! : number @homeModule.Mutation('INCREMENT') public INCREMENT! :(num: number) = > void
  @homeModule.Mutation('DECREMENT') public DECREMENT! :(num: number) = > void
  @homeModule.Mutation('MESSAGE') public MESSAGE! :(data: {message: string}) = > void
  @homeModule.Action('getTodayWeather') public getTodayWeather! :(payload: {city: string}) = > Promise<Ajax.AjaxResponse>

  @Watch('content', { immediate: true.deep: true })
  public onPersonChanged(val: WeatherContent): void {
    console.log(val)
    this.INCREMENT(1)}// Instead of computed writing
  get lowTemperature(): string {
    return this.content.low
  }

  public created() {
    console.log(this.message) // -> store.home.state.message
    console.log(this.count)   // -> store.home.getters.count
    this.INCREMENT(2) // -> store.commit('home/INCREMENT', 2)
    this.getCityWeather(this.city)
  }

  // Methods can be defined directly without being wrapped in methods
  public getCityWeather(city: string): void {
    this.getTodayWeather({ city }).then((res: Ajax.AjaxResponse) = > { // -> store.dispatch('home/foo', { city: city })
      const { low, high, type } = res.data.forecast[0]
      this.MESSAGE({ message: type })
      this.content = { low, high, type }
    })
  }
}
Copy the code

Fix $refs error

$refs: $refs: $refs: $refs: $refs: $refs:

  • The first solution is to change the variable to HTMLInputElement, where type assertions are required
test() {
  let inputBox: HTMLInputElement = this.$refs.inputBox as HTMLInputElement
  inputBox.blur()
}
Copy the code
  • The second solution (mentioned in the code example above):$refs! : {}

The editor will also indicate what methods are in the component when you type this.$refs.header. The editor prompts you for properties and methods in the Header component

Suggested writing order for components

With reference to the best practices within the working group, the following recommendations are given

Component references, mixins, filters, etc. are placed in the @Component at the beginning of the line, and decorators must be placed at the top. Order within a class:

  • data

  • @Prop

  • @State

  • @Getter

  • @Action

  • @Mutation

  • @Watch

  • Lifecycle hook

    • BeforeCreate (lifecycle hooks from top to bottom)

    • created

    • beforeMount

    • mounted

    • beforeUpdate

    • updated

    • activated

    • deactivated

    • beforeDestroy

    • destroyed

    • ErrorCaptured (The last lifecycle hook, this one is used less often)

  • Routing hooks

    • beforeRouteEnter

    • beforeRouteUpdate

    • beforeRouteLeave

  • computed

  • methods

conclusion

At the beginning, I learned the process of Microsoft introducing TS into VUE projects, established a preliminary concept by referring to TypeScript VUE Starter, and explored the posture of large VUE projects introducing TypeScript after learning related TypeScript concepts and some common practices. I hope to give you some tips and ideas, and finally recommend building a repository by following this tutorial. Welcome to Star&fork.