Javascript is very flexible to use because of its weak typing.
This also buried a lot of hidden dangers for large-scale projects and collaborative development of many people. If it is your own private business, it doesn’t matter, mainly external interfaces and public methods, it is a headache to connect. Mainly in the following aspects:
- Parameter types are not verified and are passed in any way, sometimes causing unknown problems due to type conversion.
- Interface documentation is not standard, every time you have to read the code to know what to pass, how to pass
- The interface is written to the specification, but there is a lot of code in the common library that handles type validation
This is very bad for engineering standardization. We decided to introduce typescript for strong validation at the code level.
An overview of
The original VUE project access to TS mainly includes the following steps:
- Install typescript-related NPM packages
- Modify the Webpack and TS configuration files
- Project public library and VUE file modification
Ok, here we go
1. Install typescript related NPM packages
There’s a very important point to note here:
Upgrade typescript depending on your local environment
This is a problem that many first-time users will encounter.
Because only saw the official website of the tutorial, step by step installation found all kinds of errors. The main problem is that the Webpack version doesn’t match, or some other NPM package version doesn’t match
Take my local example:
My native typescript environment is Webpack3, so if I install the latest version of typescript, the console will report that the WebPack version is too low.
So you can either upgrade your Webpack to Webapck4 or use the typescript version that matches it.
I chose the latter, because upgrading your project directly to WebAPck4 takes longer. The scaffolding we use is uniform within the company. It integrates many common underlying services. Upgrading webpack4 is more of a hassle, not to mention the fact that the project is on a tight schedule, you know.
Here are the packages I installed and their versions:
- “Typescript “: “^3.1.4” (this is required, TS library)
- “Ts-loader “: “^3.5.0” (Identify TS laoder)
- “Tslint “: “^5.11.0” (tsLint validation library)
- “Tslint-loader “: “^3.5.4” (tslint-loader)
- “Tslint-config-standard “: “^8.0.1” (for the tslint default validation rule)
- “Vue property-decorator”: “^7.2.0” (for ts syntax in.vue files)
2. Modify the configuration file
- Modify webpack configuration file (add TS related configuration)
base: {
entry: {
...
app: resolve('src/main.ts') // Change main.js to main.ts}... resolve: { ... extensions: ['vue'.'.js'.'.ts']} module: {rules: [..., {// add ts recognition to filestest: /\.ts$/,
exclude: /node_modules/,
enforce: 'pre',
loader: 'tslint-loader'
}, {
test: /\.tsx? $/, loader:'ts-loader',
exclude: /node_modules/,
options: {
appendTsSuffixTo: [/\.vue$/],
}
}
]
}
Copy the code
Note: after main.js is changed to main.ts, some modifications are required.
main.ts
import Vue from 'vue'
import { legic } from '@/lib/legic.ts'. // Add custom methods to vue objectsdeclare module 'vue/types/vue' {
interface Vue {
$legicPrototype: Function}}$legic= legic ... ; (window as any).vm = new Vue({ el:'#app',
router,
store,
template: '<App/>',
components: { App }
})
Copy the code
There are a couple of caveats here
- [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] You need to declare this through declare
- Using the window object in main.ts needs to be written as window as any
- Create tslint.json in the root directory (similar to eslint, where a validation standard is set)
{
"extends": "tslint-config-standard"."globals": {
"require": true}}Copy the code
- Create tsconfig.json (typescript configuration file) in the root directory
{
"compilerOptions": {// Compile target platform"target": "es5"// Output directory"outDir": "./dist/"// Add the syntax needed to parse, otherwise TS will detect an error."lib": ["es2015"."es2016"."dom"], // Module parsing"moduleResolution": "node"// Specify which module system code to generate"module": "esnext"// There is an error with an implied any type on expressions and declarations"noImplicitAny": false// When compiling ts files into js files, generate the corresponding map file"sourceMap": true// Allows javascript files to be compiled"allowJs": true// Specify the base directory"baseUrl": ". /"// Enable the decorator"experimentalDecorators": true// Remove the comment"removeComments": true."pretty": true// is relative to"baseUrl"parsing"paths": {
"vue": ["node_modules/vue/types"]."@ / *": ["src/*"]}},"include": [
"src/**/*"]."exclude": [
"node_modules"]}Copy the code
- Create sfc.d.ts in SRC (declare global variables, class, module, function, namespace)
The main thing we’re doing here is letting TS recognize.vue files, window objects, and some modules
For details about how to use declare, see here
/** * Files that tell TypeScript *. Vue files can be handed to the vue module for processing *. Again, TypeScript only recognizes.ts files by default, not.vue filesdeclare module "*.vue" {
import Vue from 'vue'
exportDefault Vue} /** * tells TypeScript that the window is a global object and can be used directly, so that window.xx = 123 does not report an error */declareVar window: any /** * Import VueLazyLaod from'vue-lazyload'The writing of * /declare module 'vue-lazyload'
declare module '@zz/perf/vue'
declare module 'raven-js'
declare module 'raven-js/plugins/vue'
Copy the code
Change SRC /main.js to main.ts
Project reform
This part is the most troublesome, mainly a few large pieces
-
Base library transformation
If your base library references a large number of NPM packages, then congratulations, this part of your transformation costs will be much lower.
If you write a significant portion of your lib by hand, congratulations…
We have our own lib library with a large number of self-maintained JS files. So if you’re going to do a TS retrofit, you have to change everything.
Example: getParam in lib/url.js (not advanced, but easy to read and compatible)
exportDefault class URL{/** * @memberof URL * @summary Gets the specified parameter * @ in the current page connectiontype {function} * @param {string} param1 - If param2 is undefined, param1 is the key of the specified parameter from the current page URL. If param2 is not empty, Param1 is the specified URL * @param {string} param2 - This parameter is optional. If param2 exists, the key * @ of the parameter is obtained from the specified param1 connectionreturn {string|null}
*/
static getParam (param1, param2) {
let url = ' '
letparam = null; // If there is only one parameter, the default is to get the parameter from the current page linkif (typeof param2 === 'undefined') {
url = window && window.location.href || ' 'Param = param1 // Get parameters from the specified URL}else{url = param1 param = param2} // ExcludehashImpact url = url.split(The '#') [0]if (url.indexOf('? ') > -1) {
url = url.split('? ')[1]
}
const reg = new RegExp('(^ | &)' + param + '= (/ ^ & *) [& # $] *'.'i')
const rstArr = url.match(reg)
if(rstArr ! == null) {return decodeURIComponent(rstArr[2])
}
return null
}
...
}
Copy the code
The modified file is lib/url.ts
exportDefault class URL {/** * @memberof URL * @summary Gets the specified parameters in the URL * @type {function} * @param {string} param1 - If param2 is undefined, param1 is the key of the specified parameter from the current page URL. If param2 is not empty, Param1 is the specified URL * @param {string} param2 - This parameter is optional. If param2 exists, the key * @ of the parameter is obtained from the specified param1 connectionreturn{string|null} */ static getParam (param1: string, param2? : string): string {let url: string = ' '
letParam = null // If there is only one parameter, the default parameter is obtained from the current page linkif (typeof param2 === 'undefined') {
url = window && window.location.href || ' 'Param = param1 // Get parameters from the specified URL}else {
url = param1
param = param2
}
url = url.split(The '#') [0]if (url.indexOf('? ') > -1) {
url = url.split('? ')[1]
}
const reg = new RegExp('(^ | &)' + param + '= (/ ^ & *) [& # $] *'.'i')
const rstArr = url.match(reg)
if(rstArr ! == null) {return decodeURIComponent(rstArr[2])
}
return null
}
...
}
Copy the code
If you want to change the way typescript recommends multiple calls to a method, you can use method overloading.
I didn’t use it because I didn’t want to change the way the page was used.
Note: For a large project, we do not recommend a TS transformation for all files.
We prefer to adopt a progressive approach, one by one, without affecting the original page. Specific schemes will be introduced later
- Vue file modification
src/components/helper/newUser/index.vue
<template>... </template> <script> import { LEGO_ATTR, initLegoData, legic } from'@/lib/legic'
import { getMyProfile } from '@/api/helper'
import { toast } from '@/lib/ZZSDK'
import myComponent from './myComponent.vue'
let flag = false// Whether to send the video to the buried pointexportDefault {components: {// custom component myComponent},data () {
return{// User portrait portrait:' ', // nickName:' ', // Whether to click play isPlay:false}},mounted () {
this.initData()
initLegoData({
type: 'newUserGuide'
});
legic(LEGO_ATTR.newUserGuide.SHOW);
},
methods: {
initData () {
getMyProfile().then(data => {
console.log('data', data)
const { respData } = data
this.portrait = respData.portrait || ' '
this.nickName = respData.nickname || ' '
}).catch(err => {
toast({ msg: err })
})
},
goPageClick (type) {
switch (type) {
case 'SUN':
legic(LEGO_ATTR.newUserGuide.SUNVILLAGECLICK)
break
case 'FOOTBALL':
legic(LEGO_ATTR.newUserGuide.FOOTBALLCLICK)
break
case 'SIGN':
legic(LEGO_ATTR.newUserGuide.SIGNCLICK)
break
default:
return}},videoClick () {
if (flag) {
return
} else {
flag = true
legic(LEGO_ATTR.newUserGuide.SIGNCLICK)
this.isPlay = true
this.$refs.video.play()
}
}
}
}
</script>
<style lang="scss"scoped>... </style>Copy the code
After transforming
<template>... </template> <script lang="ts">
import { LEGO_ATTR, initLegoData, legic } from '@/lib/legic'
import { getMyProfile } from '@/api/helper.ts'
import { toast } from '@/lib/ZZSDK'
import { Component, Vue } from 'vue-property-decorator'
import test from './test.vue'
let flag: boolean = false// If you want to send a video, click on @component ({components: {test}})exportDefault class NewUser extends Vue {// Portrait =' '// Username nickName =' '// Whether to hit Play isPlay =false
mounted (): void {
this.initData()
initLegoData({
type: 'newUserGuide'
});
legic(LEGO_ATTR.newUserGuide.SHOW)
}
initDataGetMyProfile (). Then ((data: any) => {console.log()'data', data)
const { respData } = data
this.portrait = respData.portrait || ' '
this.nickName = respData.nickname || ' '
}).catch((err: string) => {
toast({ msg: err })
})
}
goPageClick (type: string) {
switch (type) {
case 'SUN':
legic(LEGO_ATTR.newUserGuide.SUNVILLAGECLICK)
break
case 'FOOTBALL':
legic(LEGO_ATTR.newUserGuide.FOOTBALLCLICK)
break
case 'SIGN':
legic(LEGO_ATTR.newUserGuide.SIGNCLICK)
break
default:
return}}videoClick () {
if (flag) {
return
} else {
flag = true
legic(LEGO_ATTR.newUserGuide.SIGNCLICK)
this.isPlay = true
this.$refs.video['play']()
}
}
}
</script>
<style lang="scss"scoped>... </style>Copy the code
Mycomponent. vue is omitted before the modification, and only the modified components are shown here
<template>
<div class="main">{{title}}{{name}}</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component
export default class MyComponent extends Vue {
@Prop({ type: String, default: ' ' })
name: string
title: string = 'hello'
}
</script>
<style lang="scss" scoped>
.main{
display: none;
}
</style>
Copy the code
Here are some things to note:
- Ts does not recognize. Vue files by default, so you need to declare this in sfC. d.ts files and add the. Vue suffix when importing vue components
- Introduce the vue-property-decorator plug-in. Use modifiers to register components so that data, prop, and function are called flat (which is officially recommended)
- Ts import import file, if not suffix, the default is js file. If the JS file does not exist, the TS file is identified
- For router use, normal push operations are ok, but history is not recognized. Such as this.Router [‘history’].current.query.xxx (We currently use it this way, there’s a better way you can correct it.)
Now let’s talk about the transformation scheme mentioned above:
In fact, here mainly involves. Vue file and lib library transformation, vue file has nothing to say, one by one can be changed. Lib files, here I suggest:
- Keep the original js file at the beginning and do not delete it. In this way, files that have not yet been modified can continue to be used
- Create a new ts file, such as util.js in lib and util.ts
- The newly modified vue files are all imported into the lib library xx.ts (with the.ts suffix), such as import Util from ‘@/lib/ Util.
Ok, the above is the whole process of our transformation. What can I point out? Everybody learn from each other