vue中使用typescript的踩坑记录

2,402 阅读5分钟

什么是typescript

TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。TypeScript 编译工具可以运行在任何服务器和任何系统上。TypeScript 是开源的。

以上是ts中文网上给出的定义,我的理解就是在js的原有基础上增加了类型的概念。

它能做什么

也许有人会问,加入了类型对于我们平时的开发有什么好处呢?毕竟从代码规范角度来讲,eslint已经可以做到绝大部分的事情了。从一些规范来讲,eslint确实可以统一许多规范,声明方式,缩进,代码编写风格等等。但这些属于相对静态的代码检查,如果定义了一下一个函数

function myPush(arr, val) {
	arr.push(val)
    return arr
}

myPush([], 1) // [1]
myPush(1, 1) // error, arr.push is not a function
myPush('', 1) // error, arr.push is not a function

由于缺乏一些异常情况的处理(当然,这可以通过一些单元测试或者是通过增强代码健壮性来避免这种情况的发生),作者在使用这个函数的时候并不会出现问题,但是如果是一个公用的方法,那么就有可能因为参数的传递错误导致js报错而毫无感知。而如果使用ts开发的话则可以较好的避免此类问题的发生。

function myPush(arr: any[], val: any): any[] {
	arr.push(val)
    return arr
}

myPush([], 1) // [1]
myPush(1, 1) // 数字类型1不能赋值给类型 any[] 的参数
muPush('', 1) // string类型不能赋值给类型 any[] 的参数

这里值得注意的是,以上代码所表现出的错误,是在编写代码时实时提示的,即并非运行时所报出来的错误。

结合vue2.x的实践

尤大大在前段时间发布了vue3.0的beta版,几大特性的更新应接不暇,更重要的是,Evan使用ts全面重写了vue。并且作者本人也在知乎上回答了一下关于vue2.x对于ts的一些不完美的地方和原因。详见www.zhihu.com/question/31… 由于项目中使用的是vue2.x的版本,因此针对于ts在vue2.x中的应用做了一些实践。

首先,我们需要了解一些关于ts的基础概念,网上的资料还是很多的,官网的介绍也比较完成,详见www.tslang.cn/docs/handbo…

我们的项目中,通常会包含.vue和.js两种类型的后缀文件(仅以和业务相关的代码为主,其他类型不做考虑)。因此,改造可以大体分为两个步骤:

改造js文件

由于ts是js的超集,因此我们直接将js后缀改为ts后缀是不会出现影响的,只不过没有应用到任何ts相关的知识而已。

// 改造前
function getUrlParams(href = window.location.href) {
    const [hashFront, hashBack] = href.split('#')
    let params = {
        front: {},
        back: {},
    }
    if (hashFront && hashFront.indexOf('?') > -1) {
        params.front = qs.parse(hashFront.split('?')[1])
    }
    if (hashBack && hashBack.indexOf('?' > -1)) {
        params.back = qs.parse(hashBack.split('?')[1])
    }
    return params
}


// 改造后
interface IParamsItem {
    token?: string;
    [propName: string]: any;
}
/**
 * url中参数合集
 */
interface IParams {
    front: IParamsItem
    back: IParamsItem
}

/**
 * 获取url中所有参数 hash前和hash后
 * @param(string) href url链接 默认为当前url
 */
function getUrlParams(href: string = window.location.href) {
    const [hashFront, hashBack] = href.split('#')
    let params: IParams = {
        front: {},
        back: {}
    }
    if (hashFront && hashFront.indexOf('?') > -1) {
        params.front = qs.parse(hashFront.split('?')[1])
    }
    if (hashBack && hashBack.indexOf('?') > -1) {
        params.back = qs.parse(hashBack.split('?')[1])
    }
    return params
}

可以看到,代码不仅没少,反而还多了。但是强类型的好处就在于,妈妈再也不需要担心有人往我这个方法里面瞎传参数啦。

js文件的改造相对还是比较简单的,毕竟不设计到其他框架,只需要你在编写过程中按照官网的文档提示即可,不在赘述。

改造.vue文件

如果你在vue-cli的提示选项中选择了ts,那么cli会帮你做好很多事情,依赖包引入,tsconfig.json文件,*.d.ts声明文件等。

tsconfig.json文件,用于配置一些ts的配置信息,里面具体字段的对应信息官网上也有详细的介绍。www.tslang.cn/docs/handbo…

常见错误是对于webpack中的alias的依赖引入路径,不仅需要再webpack配置中声明,还需要在tsconfig中声明,且自定义的alias不能使用@开头(自身实践如此,如果是其他原因望指正)

*.d.ts声明文件,官网上也有介绍。www.tslang.cn/docs/handbo…

但是我在看完官网上的介绍的时候还是稀里糊涂,因此针对vue我又去查阅了一些其他资料,声明文件主要解决了在改造过程中最常出现的几个问题。

  1. 使用类似this.$axios的全局对象使用的报错。
  2. 引入.vue类型的文件解析报错。
  3. 有一些全局的变量使用报错,我的项目中是环境变量NODE_ENV。

关于以上的几个问题都可以通过声明文件解决。

做完以上工作,就要开始准备大刀阔斧的改代码了!

在vue文件中的script标签中加上lang="ts",标签内就变成了可以使用ts语法的模块了。在vue中,我们通常会有几种属性或者声明周期的应用,正常的写法如下

// 改造前
<script>
import comp from '@/components/comp.vue' // 引入vue文件必须写上后缀,否则无法识别
import checkPhone from '@/util/check-phone' // 引入ts文件则不需要加后缀

export default {
	components: {
    	comp,
    },
    props: {
    	age: {
        	type: String,
            default: '',
        }
    },
	data() {
    	return {
        	name: '',
    	}
	},
    created() {
    	console.log(this.name)
    },
    watch: {
    	name: function (n) {
        	console.log(`我的名字是${n}`)
        }
    }
    computed: {
    	fullName() {
        	return `${this.name}-全名`
        }
    },
    methods: {
    	getName() {
        	console.log(this.name)
        }
    }
}
</script>

以上只列举了一些典型的使用

// 改造后
import { Component, Vue, Prop, Watch } from 'vue-property-decorator'
import comp from '@/components/comp.vue' // 引入vue文件必须写上后缀,否则无法识别
import checkPhone from '@/util/check-phone' // 引入ts文件则不需要加后缀

@Component({
	components: {
    	comp,
    },
    created() {
    	console.log(this.name)
    },
})

export default class extends Vue {
	@Prop({
    	type: String,
        default: '',
    })
    private age: string
    
    private name: string = ''
    
    @Watch('name')
    private onNameChange(n) {
    	console.log(`我的名字是${n}`)
    }
    
    private get fullName() {
    	return `${this.name}-全名`
    }
    
    private getName() {
        console.log(this.name)
    }
}

以上写法上有了一些变化,更重要的是对于 vue-property-decorator 这个依赖包的使用,这个依赖包提供了一些装饰器的写法,让我们更方便更迅速的完成页面的改造。

github.com/kaorun343/v… (vue-property-decorator 的文档)

vue官方推荐的是 vue-class-component 这个依赖包, vue-property-decorator 可以理解为是 vue-class-component 的增强版,其中的 Component 完全继承于 vue-class-component,剩下的则是一些新增的装饰器。

使用vuex

项目会有使用vuex的情况,我们针对vuex的改造也有相同的方案。详见文档 github.com/championswi…

网上的文章比较多,不在赘述。

代码规范

在此之前曾经有tslint的说法存在,但是官方已经声明不在维护tslint,推荐使用eslint作为统一规范的方式。官方团队不再维护 typescript-eslint/parser ,将解释器转为 @typescript-eslint/parser 。

这意味着我们还可以沿用之前的eslint规范,只是在其中需要注入一些特殊的规范,eslint大部分以airbnb-base依赖作为框架复用,ts我使用的是 eslint-config-alloy/typescript

最后

改造过程还是比较辛苦的,需要兼顾以前的逻辑,由于刚刚接触ts,很多语法的使用上也不太熟悉;同时由于第一次使用,其实我对于怎么样去编写一个好的ts处于一个朦胧的状态;比如什么情况下定义一个类,什么样子的接口应该放在全局等等;如果后面有了思路会再写一篇文章。关于未来vue3.0的到来,可能会将ts的热度再提升一波,希望大家早做准备吧~