Writing in the front

TypeScript has been around for a long time and has been used by many big companies and projects. Last month, I also formally followed up a large-scale operation and maintenance project for the group.

The project is roughly divided into the following large modules

  • One-stop management platform
  • Scale operation and maintenance capability
  • Preplan platform
  • Inspection platform
  • Full link pressure measurement

Each module has to do a lot of things, because it involves the business of the company, I will not list some specific things to do here, anyway, the overall scale of the project is still very large.

One, about the selection

After doing some technical research, combined with the magnitude of development and maintenance costs after the project. Finally, my colleagues and I reached the same conclusion on the technical selection, and the final selection was Vue Newest Family Bucket + TypeScript.

The question is, why should large projects use TypeScript, but not ES6 or 7?

That’s not to say, but I personally prefer to use TypeScript in large collaborative projects. Here are some of my own thoughts after doing my research

  1. First, TypeScript has a type system and is a superset of JavaScript. What JavaScript can do, it can do. It can do things that JavaScript can’t.

  2. Secondly, TypeScript is mature, there are many relevant materials on the market, and most libraries and frameworks support TypeScript well.

  3. Then, while it’s still good, it’s still being developed and improved, with new features coming in all the time

  4. JavaScript is weakly typed and has no namespaces, making it difficult to modularize, making it less convenient in large collaborative projects

  5. Vscode, WS, and other editors are friendly to TypeScript support

  6. TypeScript supports type validation for components and businesses, for example

    // Define enumeration
    const enum StateEnum {
      TO_BE_DONE = 0,
      DOING = 1,
      DONE = 2
    }
    
    // Define the item interface
    interface SrvItem {
      val: string.key: string
    }
    
    // Define the service interface
    interface SrvType {
      name: string.key: string, state? : StateEnum,item: Array<SrvItem>
    }
    
    // Then define the initial value. (If you do not specify the type, you must report an error.)
    const types: SrvType = {
      name: ' '.key: ' '.item: []}Copy the code

    With the editor, if you do not follow the defined type, the editor itself will give you an error, rather than waiting for compilation to report the error

  7. Command space + interface declaration more convenient type verification, to prevent code irregularities

    For example, you define the Ajax return type in an ajax.d.ts file

    declare namespace Ajax {
      // Axios returns the data
      export interface AxiosResponse {
        data: AjaxResponse
      }
    
      // Request the interface data
      export interface AjaxResponse {
        code: number.data: object | null | Array<any>,
        message: string}}Copy the code

    It can then be used when requested

    this.axiosRequest({ key: 'idc' }).then((res: Ajax.AjaxResponse) = > {
      console.log(res)
    })
    Copy the code
  8. You can use generics to create reusable components. Let’s say you want to create a generic method that has the same parameter type as the return value type

    function foo<T> (arg: T) :T {
      return arg
    }
    let output = foo('string') // type of output will be 'string'
    Copy the code

    For example, you want to use generics to lock the type used in your code

    interface GenericInterface<T> {
      (arg: T): T
    }
    
    function foo<T> (arg: T) :T {
      return arg
    }
    
    // Lock myFoo to pass only the number parameter. If you pass any other type of parameter, you will get an error
    let myFoo: GenericInterface<number> = foo
    myFoo(123)
    Copy the code

In short, there are many benefits to using TypeScript that I won’t list here, but those who are interested can check them out

2. Infrastructure

1. Initialize the structure

I used the latest version of scaffolding vue-CLI 3 to initialize the project, and the initialization options are as follows

The generated directory structure is as follows

├ ─ ─ public / / static page ├ ─ ─ SRC / / home directory ├ ─ ─ assets/resources/static ├ ─ ─ the components / / component ├ ─ ─ page views / / ├ ─ ─ App. Vue/page/main entrance ├ ─ ─ Ts // The main entry for the script..ts // The main entry for the script..ts // The main entry for the script..ts // The main entry for the script..ts // The main entry for the script..ts // The configuration of the PWA..ts Exercises ├── store.ts // Exercises ├── store.ts // Exercises ├── ─ tests.js // Exercises ├── package.json // ├── tsconfig.json // ts configuration ├── tslint.json // tsLint configurationCopy the code

2. The structure after modification

Obviously, these can not meet the normal business development, so I made a version of the infrastructure transformation. After the renovation, the project structure is as follows

├ ─ ─ public / / static page ├ ─ ─ scripts / / related configuration script ├ ─ ─ SRC / / home directory ├ ─ ─ assets/resources/static ├ ─ ─ filters / / filter ├ ─ ─ lib / / global plugin ├ ─ ─ the router For example, for example, for example, for example, for example, for example, for example, for example, for example, for example, for example, for example, for example, for example, for example, for example, for example, for example, for example, for example Exercises ── ─ app.vue // Exercises ── main.ts // Exercises ── registerserviceworker.ts // Exercises ── tests // Test Case Exercises ── ─ editorConfig // Edit the Related configuration Exercises ──.npmrc // NPM Source Configuration Exercises ──.postcssrc.js // PostCSS Configuration Exercises ─ babel.config.js // Preset record ├ ─ ─ cypress. Json / / e2e plugins ├ ─ ─ f2eci. Json / / deployment related configuration ├ ─ ─ package. The json / / dependence ├ ─ ─ the README. Md / / project README ├ ─ ─ ├── tslint.json // Tslint.js // Webpack ├── vue.config.jsCopy the code

3. Module transformation

Next, I will introduce the transformation of some modules in the project

I. Routes are loaded lazily

Here we use webPack’s load on demand import, which puts the same module in the same chunk and writes it to router/index.ts

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export default new Router({
  routes: [{path: '/'.name: 'home'.component: () = > import(/* webpackChunkName: "home" */ 'views/home/index.vue')}]})Copy the code

Ii. Axios encapsulation

Write the axios configuration in utils/config.ts (only a few are listed, please configure them for your own business)

import http from 'http'
import https from 'https'
import qs from 'qs'
import { AxiosResponse, AxiosRequestConfig } from 'axios'

const axiosConfig: AxiosRequestConfig = {
  baseURL: '/'.// Data processing after the request
  transformResponse: [function (data: AxiosResponse) {
    return data
  }],
  // Query the object serialization function
  paramsSerializer: function (params: any) {
    return qs.stringify(params)
  },
  // Set the timeout to s
  timeout: 30000.// Whether to carry the Token in the cross-domain
  withCredentials: true.responseType: 'json'./ / XSRF Settings
  xsrfCookieName: 'XSRF-TOKEN'.xsrfHeaderName: 'X-XSRF-TOKEN'.// Maximum number of forwards for Node.js
  maxRedirects: 5.// Maximum response data size
  maxContentLength: 2000.// Customize the error status code range
  validateStatus: function (status: number) {
    return status >= 200 && status < 300
  },
  / / for the node. Js
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true})}export default axiosConfig
Copy the code

Next, you need to do some global interception in utils/api.ts. Here I have the interceptor to cancel repeated requests. If your business does not need it, please remove it yourself

import axios from 'axios'
import config from './config'

// Cancel duplicate requests
let pending: ArrayThe < {url: string.cancel: Function} > = []const cancelToken = axios.CancelToken
const removePending = (config) = > {
  for (let p in pending) {
    let item: any = p
    let list: any = pending[p]
    // Execute the function body when the current request exists in the array
    if (list.url === config.url + '&request_type=' + config.method) {
      // Cancel
      list.cancel()
      // Remove the record from the array
      pending.splice(item, 1)}}}const service = axios.create(config)

// Add a request interceptor
service.interceptors.request.use(
  config= > {
    removePending(config)
    config.cancelToken = new cancelToken((c) = > {
      pending.push({ url: config.url + '&request_type=' + config.method, cancel: c })
    })
    return config
  },
  error= > {
    return Promise.reject(error)
  }
)

// Return status determination (add response interceptor)
service.interceptors.response.use(
  res= > {
    removePending(res.config)
    return res
  },
  error= > {
    return Promise.reject(error)
  }
)

export default service
Copy the code

For convenience, we also need to define a fixed set of axios return formats, which we can define globally. Write to the types/ajax.d.ts file

declare namespace Ajax {
  // Axios returns the data
  export interface AxiosResponse {
    data: AjaxResponse
  }

  // Request the interface data
  export interface AjaxResponse {
    code: number.data: any.message: string}}Copy the code

Next, we will put all Axios into vuex actions for unified management

Iii. Vuex modular management

Under store, a folder represents a module. The store directory is as follows

├ ─ ─ 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

Manages the interfaces of related modules in home/ interface-ts

export interface HomeContent {
  name: stringm1? :boolean
}
export interface State {
  count: number, test1? :Array<HomeContent>
}
Copy the code

Then define the relevant vuex module contents at home/index.ts

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

interface GetTodayWeatherParam {
  city: string
}

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

const mutations = {
  INCREMENT (state: State, num: number) {
    state.count += num
  }
}

const actions = {
  async getTodayWeather (context: { commit: Commit }, params: GetTodayWeatherParam) {
    return request.get('/api/weatherApi', { params: params })
  }
}

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

Then we can use it in the page

<template>
  <div class="home">
    <p>{{ count }}</p>
    <el-button type="default" @click="INCREMENT(2)">INCREMENT</el-button>
    <el-button type="primary" @click="DECREMENT(2)">DECREMENT</el-button>
    <el-input v-model="city" placeholder="Please enter city" />
    <el-button type="danger" @click="getCityWeather(city)">For the weather</el-button>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import { State, Getter, Mutation, Action } from 'vuex-class'

@Component
export default class Home extends Vue {
  city: string = 'Shanghai'
    
  @Getter('count') count: number
  @Mutation('INCREMENT') INCREMENT: Function
  @Mutation('DECREMENT') DECREMENT: Function
  @Action('getTodayWeather') getTodayWeather: Function

  getCityWeather (city: string) {
    this.getTodayWeather({ city: city }).then((res: Ajax.AjaxResponse) = > {
      const { low, high, type } = res.data.forecast[0]
      this.$message.success(`${city}Today:${type} ${low} - ${high}`)}}}</script>
Copy the code

As for more modifications, I won’t cover them here. The following sections describe some of the ways ts can be written in a Vue file

3, the use of ts in vue

1, vue – property – the decorator

Here the single-page component is written using the Vue-property-decorator library, which is completely dependent on the Vue-class-Component and is officially recommended by VUE.

The vue-property-decorator contains eight decorators to solve this problem. They are: (@component ({}), props, data, etc

  • @EmitTo specify an event emit, you can use this modifier or use it directlythis.$emit()
  • @InjectSpecify dependency injection)
  • @MixinsMixins injection
  • @ModelSpecify the model
  • @PropSpecify the Prop
  • @ProvideSpecify the dojo.provide
  • @WatchSpecify the Watch
  • @Component export from vue-class-component

An 🌰

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

@Component
export class MyComponent extends Vue {
  dataA: string = 'test'
    
  @Prop({ default: 0 })
  propA: number

  // watcher
  @Watch('child')
  onChildChanged (val: string.oldVal: string) {}
  @Watch('person', { immediate: true.deep: true })
  onPersonChanged (val: Person, oldVal: Person) {}

  // See the github address above for details on other modifiers
}
Copy the code

When you parse it, it becomes

export default {
  data () {
    return {
      dataA: 'test'}},props: {
    propA: {
      type: Number.default: 0}},watch: {
    'child': {
      handler: 'onChildChanged'.immediate: false.deep: false
    },
    'person': {
      handler: 'onPersonChanged'.immediate: true.deep: true}},methods: {
    onChildChanged (val, oldVal) {},
    onPersonChanged (val, oldVal) {}
  }
}
Copy the code

2, vuex – class

Vuex-class is a library based on Vue, vuex, and Vue-class-Component. Like vue-property-decorator, it also provides four decorators and a namespace. Solves the inconvenience of using vuex in.vue files.

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

Copy an official 🌰

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

So much for the use of ts in.vue files. I also believe that friends see this, sugar has a certain understanding of its general grammar

3. Some suggestions

  • If you define.d.tsFile, please restart the service so that your service can recognize the module you defined, and restart vscode so that the editor can also recognize (really disgusting)
  • Set up yourtsconfigRemember, for examplestrictPropertyInitializationSet it to false, otherwise you must give it an initial value when you define a variable.
  • Be sure to manage your routing hierarchy, or even re won’t save you
  • At the business level, do a good job of type detection or enumeration definition, which not only facilitates development, but also can quickly locate problems
  • To use VUEX across modules, use it directlyrootGetters
  • If you need to change the theme of a component library, open a single file for centralized management, other components in a separate file to change, otherwise the compilation speed will be worrying
  • We can reuse the good things developed by others in the team, and try not to develop the second time, otherwise the wasted time may not only be the development time, but also the code review time

There’s a lot more, but more for you to find out. Next, I’ll talk about some specifications for teamwork on large projects

4. How to work as a team

For a large project, many people must work together in parallel. There is not only the cooperation of the front end team, but also the demand exploration (SI) and discussion (BI) with the product classmates, as well as the coordination with the back-end students, and even the configuration of some services needs to be carried out by oneself or relying on SRE.

1. Front-end development specifications

Since the project is based on VUE + TS and is a collaborative effort, the development specification is definitely necessary to make parallel development easier. Here are some of the rules I made for your reference (just for reference).

I. Page development placement order

  • HTML
  • TypeScript
  • CSS
<template>
</template>

<script lang="ts">
</script>

<style lang="scss">
</style>
Copy the code

Ii. CSS rules (use BEM naming rules to avoid style conflicts, not scoped)

<template>
  <div class="home">
    <div class="home__count">{{ count }}</div>
    <div class="home__input"></div>
  </div>
</template>

<style lang="scss">
.home {
  text-align: center;
  &__count {}
  &__input {}
}
</style>
Copy the code

Iii. TS context order in the vue file

  • data

  • @Prop

  • @State

  • @Getter

  • @Action

  • @Mutation

  • @Watch

  • Lifecycle hook

    • BeforeCreate (from top to bottom according to lifecycle hooks)

    • created

    • beforeMount

    • mounted

    • beforeUpdate

    • updated

    • activated

    • deactivated

    • beforeDestroy

    • destroyed

    • ErrorCaptured (last lifecycle hook)

  • Routing hooks

    • beforeRouteEnter

    • beforeRouteUpdate

    • beforeRouteLeave

  • computed

  • methods

Component references, mixins, filters, and so on are placed inside @Component

<script lang="ts">
@Component({
  components: { HelloWorld },
  mixins: [ Emitter ]
})
export default class Home extends Vue {
  city: string = 'Shanghai'

  @Prop({ type: [ Number.String].default: 16 })
  size: number | string
  @State('state') state: StateInterface
  @Getter('count') count: Function
  @Action('getTodayWeather') getTodayWeather: Function
  @Mutation('DECREMENT') DECREMENT: Function
  
  @Watch('count')
  onWatchCount (val: number) {
    console.log('onWatchCount', val)
  }
  
  // computed
  get styles () {}
  
  created () {}
  mounted () {}
  destroyed () {}

  // methods
  getCityWeather (city: string) {}
}
</script>
Copy the code

Iv. Vuex modular management

A folder under store corresponds to a module, and each module has an interface for interface management. Specific examples are mentioned above

V. Route import posture

Routes are lazily loaded, as shown in the previous article

Vi. File naming convention

Lowercase words, separated by ‘-‘, as shown

The noun comes before the verb, as shown in the picture

Same module description first, different description later

2. Cooperate with products and backend

Keep these three points in mind:

  1. Ask (si) politely (BI)

  2. Ask (si) (bi) politely

  3. Be very polite.

Specific details I have in the knowledge of the inside of the answer, here do not repeat. Portal: the front and back ends are separated, and the data returned in the background cannot be written in the front end. What should I do?

3. Human efficiency improvement

Last point, I talked about collaboration at the development level. Here, talk about human efficiency improvement.

As we all know, whether a project can complete development + coordination + testing + launch in the scheduled time, the most important reason is everyone’s efficiency. We can’t guarantee that we’re all efficient, but we have to guarantee that we’re efficient.

Once the need comes down, the first thing we have to make sure is that we know the need. Generally for the veteran, the need to go through the mind is roughly clear about how much time to complete the need, and the novice will never have a good understanding of the time to complete.

So, how to improve their development efficiency?

  • Break the requirements into modules
  • Break down the modules into smaller details again
  • Assess the development time of the small details themselves
  • Assess the development time for some possible risk points in small details
  • Assess the time of alignment
  • Reserve time nodes for testing + BUG fixing
  • Reserve deadline (generally 1 * (1 + 0.2))
  • Schedule your development nodes in 1D (days)
  • Keep a record of the reasons for the risk, the risk points and the corresponding avoidance plan
  • If a delay is anticipated, remedial plans should be provided in time (such as overtime).
  • Keep track of the number of bugs and who bugs them (really not to blame)

conclusion

This is the end of the article. I also talked about the selection before project approval, the infrastructure construction at the initial stage of the project, the use of TS in.vue, and even some things about team cooperation in project development. But after all, the limited style of writing, a lot of points can not be explained, mostly point to the end. If you think the article is helpful to friends, please don’t be stingy with your thumbs up

If you want to know more, please join my communication group: 731175396