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
- Due to the
webpack
Is 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
- And then there’s the change
webpack
Modify the configuration ofmain.js
File formain.ts
And add it in the first line of the file// @ts-nocheck
letTS
Ignore to check this file, inwebpack.base.config.js
The corresponding change in the entry ofmain.ts
- in
webpack.base.config.js
theresolve
In theextensions
add.ts
and.tsx
.alias
Add a rule'vue$': 'vue/dist/vue.esm.js'
- in
webpack.base.config.js
addplugins
Option to addfork-ts-checker-webpack-plugin
That will bets check
Is put into a separate process, reducing development server startup time - in
webpack.base.config.js
Of the filerules
Add two configurations andfork-ts-checker-webpack-plugin
Plug-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
- In the root directory
tsconfig.json
File to supplement the corresponding configuration,src
Adding a file to a directoryvue-shim.d.ts
Declaration 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:
- Use may occur at the time of use
path
Or usename
The situation of non-standardization and non-uniformity - It is not convenient for developers to find single files corresponding to routes while maintaining old code
- To manually avoid routing
name
andpath
Does 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 in
api/**/*.ts
File and pass to the correspondingfunction
Annotate parameter request type and response type - If the structure is very simple, you don’t need to
typings/request/*.d.ts
Maintenance, directly in the encapsulation interface declaration type, if the parameter is slightly more, should be intypings/request/*.d.ts
Maintenance, 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
- The installation
@vue/componsition-api
npm i @vue/componsition-api
Copy the code
- in
main.ts
中use
Can be in.vue
The 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
- Modular ApiThe documentIn the case of more complex pages and more components, combinatorial API is better than traditional API
Options API
More flexible, you can pull logic out and encapsulate it as a separate entityuse
Function to make the component code structure clearer, but also easier to reuse business logic. - All of the composite apis
api
Need to be from@vue/composition-api
, and then useexport default defineComponent({ })
Replace the originalexport default { }
To enable the combined Api syntax andTypescript
Type derivation (script
You need to add correspondinglang="ts"
theattribute
) template
Is written with andVue2
No need to pay attention toVue3
In thev-model
And similar.native
The event modifier in theVue3
Cancel, etcbreak change
- A child component that calls a method in a parent component
setup(props, ctx)
In thectx.emit(eventName, params)
Can, toVue
Properties and methods mounted on the instance object can passctx.root.xxx
To get, including$route
,$router
Etc., recommended for ease of usesetup
The first line is declared by structurectx.root
If you have previously added attributes or methods related to business attributes to the Vue instance object, the extension module can be usedvue/types/vue
On theVue
Interface 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
- all
template
All variables, methods, and objects used in thesetup
In thereturn
Others are not needed for internal use within the page logicreturn
- The definition is recommended based on page presentation elements and user interaction with the page
setup
In the method, more complex logic details and processing of data as far as possible away from the external, keep.vue
The code in the file is logically clear - 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
- In the current
Vue2.6
Approved in version@vue/composition-api
This cannot be used with the composite Apisetup
Syntactic sugarLaterVue2.7
versionrelease
And 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 ~