javascript因为自身的弱类型,使用起来很是灵活。javascript
这也就为大型项目、多人协做开发埋下了不少隐患。若是是本身的私有业务倒无所谓,主要是对外接口和公共方法,对接起来很是头疼。主要表如今几方面:css
这就很是不利于工程标准化。因而咱们决定引入typescript进行代码层面的强校验。html
原有vue项目接入ts主要包含下面几大步骤:vue
这块有个很是重要的点须要注意:java
就是要根据你本地的环境,去升级对应版本的typescriptnode
这块是不少初次使用的同窗都会遇到的问题。webpack
由于只是看到了官网的教程,一步一步安装完发现各类报错。主要问题就是webpack版本不匹配,或者其余一些npm包版本不匹配web
我本地环境是webpack3,因此直接安装最新版本的typescript,控制台会报错webpack版本太低的问题。算法
因此你要不把本身的webpack升级到webapck4.要不就采用与之相匹配的typescript版本。typescript
我选择的是后者,由于直接给本身的项目升级到webapck4,会花费更长的时间。咱们用的脚手架是公司内部统一的。里面集成了不少底层通用的基础服务。冒然升级webpack4会带来更大的麻烦,更况且项目时间比较紧迫,你懂得。
下面是我安装的包和对应的版本:
base: {
entry: {
...
app: resolve('src/main.ts') // 把main.js改成main.ts
}
...
resolve: {
...
extensions: ['vue', '.js', '.ts']
}
module: {
rules: [
...,
{ // 加入对文件的ts识别
test: /\.ts$/,
exclude: /node_modules/,
enforce: 'pre',
loader: 'tslint-loader'
}, {
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
options: {
appendTsSuffixTo: [/\.vue$/],
}
}
]
}
复制代码
注意: main.js改为main.ts后,还要作一些改造,
main.ts
import Vue from 'vue'
import { legic } from '@/lib/legic.ts'
...
// 给vue对象添加自定义方法
declare module 'vue/types/vue' {
interface Vue {
$legic: Function
}
}
// 统计上报方法注入
Vue.prototype.$legic = legic
...
;(window as any).vm = new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
复制代码
这里有几个注意的点
- 给Vue的prototpye注入新属性和方法的话,直接用Vue.prototype.xxx = xxx ts校验是不经过的。须要经过declare声明一下
- 在main.ts中用window对象须要写成(window as any)这种方式
{
"extends": "tslint-config-standard",
"globals": {
"require": true
}
}
复制代码
{
"compilerOptions": {
// 编译目标平台
"target": "es5",
// 输出目录
"outDir": "./dist/",
// 添加须要的解析的语法,不然TS会检测出错。
"lib": ["es2015", "es2016", "dom"],
// 模块的解析
"moduleResolution": "node",
// 指定生成哪一个模块系统代码
"module": "esnext",
// 在表达式和声明上有隐含的any类型时报错
"noImplicitAny": false,
// 把 ts 文件编译成 js 文件的时候,同时生成对应的 map 文件
"sourceMap": true,
// 容许编译javascript文件
"allowJs": true,
// 指定基础目录
"baseUrl": "./",
// 启用装饰器
"experimentalDecorators": true,
// 移除注释
"removeComments": true,
"pretty": true,
// 是相对于"baseUrl"进行解析
"paths": {
"vue": ["node_modules/vue/types"],
"@/*": ["src/*"]
}
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules"
]
}
复制代码
咱们在这里主要是让ts识别.vue文件、window对象和一些module
具体declare的使用方式请看这里
/**
* 告诉 TypeScript *.vue 后缀的文件能够交给 vue 模块来处理
* 而在代码中导入 *.vue 文件的时候,须要写上 .vue 后缀。
* 缘由仍是由于 TypeScript 默认只识别 *.ts 文件,不识别 *.vue 文件
*/
declare module "*.vue" {
import Vue from 'vue'
export default Vue
}
/**
* 告诉 TypeScript window是个全局对象,直接可用,这样就不会在window.xx = 123时报错
*/
declare var window: any
/**
* 引入部分第三方库/本身编写的模块的时候须要额外声明文件
* 引入的时候,须要使用相似 import VueLazyLaod from 'vue-lazyload' 的写法
*/
declare module 'vue-lazyload'
declare module '@zz/perf/vue'
declare module 'raven-js'
declare module 'raven-js/plugins/vue'
复制代码
将src/main.js改成main.ts
这个部分是最麻烦的,主要有几大块
基础库改造
若是你的基础库引用了大量的npm包,那么恭喜你,这部分你的改形成本会低不少。
若是你的lib库有至关一部分都是本身手写的,那么,我也得恭喜你。。。
咱们本身的lib库里,有大量的本身维护的js文件。那么若是你要进行ts改造的话,统统都要改。
举个例子: lib/url.js 中的getParam (算法并不高级,就是易读、兼容性好)
export default class URL{
/**
* @memberOf URL
* @summary 获取当前页面链接中指定参数
* @type {function}
* @param {string} param1 - 若是param2为undefined,param1是指从当前页面url中获取指定参数的key, 若是param2不为空,param1为指定的url
* @param {string} param2 - 可选参数,若是param2存在,则从指定的param1链接中获取对应参数的key
* @return {string|null}
*/
static getParam (param1, param2) {
let url = ''
let param = null;
// 若是只有一个参数,默认从当前页面连接获取参数
if (typeof param2 === 'undefined') {
url = window && window.location.href || ''
param = param1
// 从指定url中获取参数
} else {
url = param1
param = param2
}
// 排除hash的影响
url = url.split('#')[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
}
...
}
复制代码
改造后的文件为:lib/url.ts
export default class URL {
/**
* @memberOf URL
* @summary 获取url中指定参数
* @type {function}
* @param {string} param1 - 若是param2为undefined,param1是指从当前页面url中获取指定参数的key, 若是param2不为空,param1为指定的url
* @param {string} param2 - 可选参数,若是param2存在,则从指定的param1链接中获取对应参数的key
* @return {string|null}
*/
static getParam (param1: string, param2?: string): string {
let url: string = ''
let param = null
// 若是只有一个参数,默认从当前页面连接获取参数
if (typeof param2 === 'undefined') {
url = window && window.location.href || ''
param = param1
// 从指定url中获取参数
} else {
url = param1
param = param2
}
url = url.split('#')[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
}
...
}
复制代码
对于一个方法多种调用方式,若是你想彻底改为typescript推荐的方式,你能够用到方法重载。
我没有用是由于我不但愿改变原有页面的使用方式。
注:对于一个大型项目来说,咱们并不建议上来就对所有的文件进行ts改造。
咱们更建议采用渐进式改造方案,在不影响原有页面的状况下,逐一改造。具体方案后面会介绍
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 // 是否发送视频点击埋点
export default {
components: {
// 自定义组件
myComponent
},
data () {
return {
// 用户头像
portrait: '',
// 用户名称
nickName: '',
// 是否点击播放
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>
复制代码
改造后
<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 // 是否发送视频点击埋点
@Component({
components: {
test
}
})
export default class NewUser extends Vue {
// 用户头像
portrait = ''
// 用户名称
nickName = ''
// 是否点击播放
isPlay = false
mounted (): void {
this.initData()
initLegoData({
type: 'newUserGuide'
});
legic(LEGO_ATTR.newUserGuide.SHOW)
}
initData () {
// 获取profile信息
getMyProfile().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>
复制代码
myComponent.vue改造前略,这里只展现改造后的组件
<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 = '您好'
}
</script>
<style lang="scss" scoped>
.main{
display: none;
}
</style>
复制代码
这里须要注意的是:
如今说下前面提到的改造方案:
这里其实主要涉及.vue文件和lib库的改造,vue文件没啥可说的,一个个改就能够了。主要说lib里面的文件,这里我建议:
ok以上就是咱们改造的所有过程。 有什么问题能够指正,你们互相学习