originally

An existing project was created two years ago. As time goes by, the number of code has exploded to nearly tens of thousands of files, but the engineering has gradually become unmaintainable. I want to give it a big change, but there are too many invasive code configurations… “, eventually introduced TypeScript, composite apis, and Vueuse in a compromise way that raised the level of engineering specification for the project. The whole process made me feel a little bit better.

Configure typescript-related first

Installation and configuration of some libraries

  1. Due to thewebpackIs the version of3.6, try several times to upgrade to4, 5,Because of a lot of configuration and intrusive code to change a lot of work gave up, so went straight to the following libraries
NPM I -D [email protected] [email protected] [email protected] [email protected]Copy the code
  1. And then there’s the changewebpackModify the configuration ofmain.jsFile formain.tsAnd add it in the first line of the file// @ts-nocheckletTSIgnore to check this file, inwebpack.base.config.jsThe corresponding change in the entry ofmain.ts
  2. inwebpack.base.config.jstheresolveIn theextensionsadd.tsand.tsx.aliasAdd a rule'vue$': 'vue/dist/vue.esm.js'
  3. inwebpack.base.config.jsaddpluginsOption to addfork-ts-checker-webpack-pluginThat will bets checkIs put into a separate process, reducing development server startup time
  4. inwebpack.base.config.jsOf the filerulesAdd two configurations andfork-ts-checker-webpack-pluginPlug-in configuration for
{
  test: /\.ts$/,
  exclude: /node_modules/,
  enforce: 'pre'.loader: 'tslint-loader'
},
{
  test: /\.tsx? $/,
  loader: 'ts-loader'.exclude: /node_modules/,
  options: {
    appendTsSuffixTo: [/\.vue$/].transpileOnly: true // disable type checker - we will use it in fork plugin}},// ...
plugins: [new ForkTsCheckerWebpackPlugin()], / / in the process of independent processing ts - the checker, cold start and hot update time shorten webpack service https://github.com/TypeStrong/ts-loader#faster-builds
Copy the code
  1. In the root directorytsconfig.jsonFile to supplement the corresponding configuration,srcAdding a file to a directoryvue-shim.d.tsDeclaration file
tsconfig.json
{
  "exclude": ["node_modules"."static"."dist"]."compilerOptions": {
    "strict": true."module": "esnext"."outDir": "dist"."target": "es5"."allowJs": true."jsx": "preserve"."resolveJsonModule": true."downlevelIteration": true."importHelpers": true."noImplicitAny": true."allowSyntheticDefaultImports": true."moduleResolution": "node"."isolatedModules": false."experimentalDecorators": true."emitDecoratorMetadata": true."lib": ["dom"."es5"."es6"."es7"."dom.iterable"."es2015.promise"]."sourceMap": true."baseUrl": "."."paths": {
      "@ / *": ["src/*"],},"pretty": true
  },
  "include": ["./src/**/*"."typings/**/*.d.ts"]}Copy the code
vue-shim.d.ts
declare module '*.vue' {
  import Vue from 'vue'
  export default Vue
}
Copy the code

Routing configuration improvements

The original route configuration is by configuring path, name, and Component, which has some disadvantages during development and maintenance:

  1. Use may occur at the time of usepathOr usenameThe situation of non-standardization and non-uniformity
  2. It is not convenient for developers to find single files corresponding to routes while maintaining old code
  3. To manually avoid routingnameandpathDoes not conflict with other routes

All routes are separated to different enumerations based on services. Enumerations prevent route path collisions, make enumeration keys more semantically defined, and use Typescript’s type derivation capabilities to quickly complete routes and find single files in one step

Why not use name, because name is just a semantic identification of the route, when we use an enumerated path, the enumerated Key is enough to act as a semantic path and there is no need for the name attribute to exist, we do not need to declare the name attribute when we declare the route, All you need is the PATH and Component fields

demo
export enum ROUTER {
  Home = '/xxx/home',
  About = '/xxx/about',}export default[{path: ROUTER.Home,
    component: () = > import( /* webpackChunkName:'Home' */ 'views/Home')}, {path: ROUTER.About,
    component: () = > import( /* webpackChunkName:'About' */ 'views/About')}]Copy the code

Constants and enumerations

In our project, we managed all constants by separating them into services/const. Now with Typescript, we can manage constants in services/ Constant and enums in services/enums.

For example, the code returned by a common interface can be declared as an enumeration, instead of requiring handwritten if (res.code === 200), you can directly obtain all the code types returned by the interface through the declared RES_CODE enumeration

// services/enums/index.ts
/** RES_CODE Enum */
export enum RES_CODE {
  SUCCESS = 200
  // xxx
}
Copy the code

Such as storage of key we can declare in the services/constant/storage. The ts

/** userInfo-storageKey */
export const USERINFO_STORE_KEY = 'userInfo'

/** User-specific keys can be declared by constructing a pure function with a business attribute argument
export const UserSpecialInfo = (userId: string) = > {
  return `specialInfo-${userId}`
}
Copy the code

Type declaration file specification

Global type declaration files are maintained uniformly in the Typings folder of the root directory (reusable data types)

The types in the process of assembling data in relatively partial services can be maintained directly in the component where they belong (data structures that are not easy to reuse).

Type encapsulation in interfaces

Request the base class to encapsulate the logic

Add the requestWrapper.ts file to the utils folder, where all subsequent request base class method wrappers can be maintained

// src/utils/requestWrapper.ts
import { AxiosResponse } from 'axios'
import request from '@/utils/request'

// The request parameter is specified to a specific type only when it is encapsulated later. Unknown is declared here, and the return value is generic S
export function PostWrapper<S> (
  url: string, data: unknown, timeout? :number
) {
  return (request({
    url,
    method: 'post',
    data,
    timeout
  }) as AxiosResponse['data']) as BASE.BaseResWrapper<S> // BASE is a namespace defined in Typings with code description
}
Copy the code

Use after encapsulation in the specific business layer

Create an index.ts file in API /user, which is concise enough to provide a type indication of what the request is and the parameters and return values of the parameters

import { PostWrapper } from '@/utils/requestWrapper'

// We need to specify the interface in the comment. We do not need to specify the type of parameters required by the comment. TS will do this for us
/** Get user information */
export function getUserInfo(query: User.UserInfoReqType) {
  return PostWrapper<User.UserInfoResType>(
    '/api/userinfo',
    query
  )
}
Copy the code
  • Interfaces that need to provide type support need to be declared inapi/**/*.tsFile and pass to the correspondingfunctionAnnotate parameter request type and response type
  • If the structure is very simple, you don’t need totypings/request/*.d.tsMaintenance, directly in the encapsulation interface declaration type, if the parameter is slightly more, should be intypings/request/*.d.tsMaintenance, avoid confusion

Now business services in the basic interface returns are through a descriptive object wrapped layer, the business data in the object request fields, based on this we encapsulate interface in typings/request/index, which s statement request returns in the base class structure, Refine specific request type declarations in specific XXX.d. ts, such as an error interface in user.d.ts, declare a global namespace user in this file to manage the request and response data types of all such job interfaces

typings/request/index.d.ts
import { RES_CODE } from '@/services/enums'

declare global {
  // * All base classes declare types here
  namespace BASE {
    // The wrapper type declaration returned by the request is provided to the specific data layer for wrapping
    type BaseRes<T> = {
      code: RES_CODE result? : T info? :string
      time: number
      traceId: string
    }
    type BaseResWrapper<T> = Promise<BASE.BaseRes<T>>
    // Paging interface
    type BasePagination<T> = {
      content: T
      now: string
      page: number
      size: number
      totalElements: number
      totalPages: number}}Copy the code
typings/request/user.d.ts
declare namespace User {

/** Response parameters */
type UserInfoResType = {
  id: number | string
  name: string
  // ...
}

/** Request parameters */
type UserInfoReqType = {
  id: number | string
  // ...
}

Copy the code

That’s the end of typescript-related stuff, and the compositional Api’s

Vue2 uses a composite Api

  1. The installation@vue/componsition-api
npm i @vue/componsition-api
Copy the code
  1. inmain.tsuseCan be in.vueThe composite API is used in the file
import VueCompositionAPI from '@vue/composition-api'
// ...
Vue.use(VueCompositionAPI)
Copy the code

Some considerations for using the composite Api in Vue2

  1. Modular ApiThe documentIn the case of more complex pages and more components, combinatorial API is better than traditional APIOptions APIMore flexible, you can pull logic out and encapsulate it as a separate entityuseFunction to make the component code structure clearer, but also easier to reuse business logic.
  2. All of the composite apisapiNeed to be from@vue/composition-api, and then useexport default defineComponent({ })Replace the originalexport default { }To enable the combined Api syntax andTypescriptType derivation (scriptYou need to add correspondinglang="ts"theattribute)
  3. templateIs written with andVue2No need to pay attention toVue3In thev-modelAnd similar.nativeThe event modifier in theVue3Cancel, etcbreak change
  4. A child component that calls a method in a parent componentsetup(props, ctx)In thectx.emit(eventName, params)Can, toVueProperties and methods mounted on the instance object can passctx.root.xxxTo get, including$route,$routerEtc., recommended for ease of usesetupThe first line is declared by structurectx.rootIf you have previously added attributes or methods related to business attributes to the Vue instance object, the extension module can be usedvue/types/vueOn theVueInterface to add business attribute related types:
typings/common/index.d.ts
// 1. Make sure to import 'vue' before declaring augmented types
import Vue from 'vue'
// 2. Specify a file with the types you want to augment
// Vue has the constructor type in types/vue.d.ts
declare module 'vue/types/vue' {
  // 3. Declare augmentation for Vue
  interface Vue {
    /** Whether the current environment is IE */
    isIE: boolean
    / /... You add them according to your own business situation}}Copy the code
  1. alltemplateAll variables, methods, and objects used in thesetupIn thereturnOthers are not needed for internal use within the page logicreturn
  2. The definition is recommended based on page presentation elements and user interaction with the pagesetupIn the method, more complex logic details and processing of data as far as possible away from the external, keep.vueThe code in the file is logically clear
  3. Before the requirement development, according to the definition of server-side interface data, to develop the interface of data and methods in the page component, you can declare the type in advance, and then implement specific methods in the development process
  4. In the currentVue2.6Approved in version@vue/composition-apiThis cannot be used with the composite ApisetupSyntactic sugarLaterVue2.7versionreleaseAnd then we’ll look at some of the othersPrecautions and restrictions

Style specification for reactive’s Store

Given the inconvenience of accessing TS in Vuex and the necessity of using Vuex scenarios, a best practice is provided in the composite Api: Declare the data that needs to be responded to in a TS file and initialize the object with a reactive package, exposing an updated method to achieve the original effect of updating state in store in Vuex, and achieve the effect of getter with computed data. Which components need to be retrieved and modified just need to be imported, and the changes can be directly responded to!

Provide a Demo, you can see the packaging of this part of the content
// xxxHelper.ts
import { del, reactive, readonly, computed, set } from '@vue/composition-api'

// Define the type of data in the store to constrain the data structure
interface CompositionApiTestStore {
  c: number
  [propName: string] :any
}

/ / initial value
const initState: CompositionApiTestStore = { c: 0 }

const state = reactive(initState)

/** The exposed store is read-only and can only be changed with the following updateStore */
export const store = readonly(state)

/** Can achieve the effect of the original Vuex getter method */
export const upperC = computed(() = > {
  return store.c.toUpperCase()
})

/** * exposes the method of changing the state object, the parameter is a subset of the state object or no parameter, if no parameter is convenient for the current object, delete all child objects, otherwise I need to update or delete */
export function updateStore(
  params: Partial<CompositionApiTestStore> | undefined
) {
  console.log('updateStore', params)
  if (params === undefined) {
    for (const [k, v] of Object.entries(state)) {
      del(state, `${k}`)}}else {
    for (const [k, v] of Object.entries(params)) {
      if (v === undefined) {
        del(state, `${k}`)}else {
        set(state, `${k}`, v)
      }
    }
  }
}
Copy the code

vueuse

Vueuse is a very easy to use library, the specific installation and use is very simple, but many features are very powerful, this part I will not expand on the details, we go to see the official documentation!

conclusion

The project to upgrade was forced to approach, project has huge even compatible with Internet explorer, use of scaffolding and related libraries are also haven’t updated version, at the beginning of the project creation will have had a lot of technical debt, causes behind the development of maintenance personnel squealed (in fact is I, project is another figure, escape…). , everyone big brother in the new project must consider the scaffolding and technology stack ah, do not predecessors dug holes later filled……

If you are also maintaining such projects and are fed up with the bad development experience, you can follow my experience to transform your project, if it helps you, please also give a one-button three-link ~