最近嘗試了一下 TypeScript,試着把一個 Vue 項目改爲了 TypeScript 的,感受還不錯。 目前 Vue 和 TypeScript 的配合還不算很完美,Vuex 和 TypeScript 的配合挺糟糕的,嘗試須要謹慎,若是想體驗一下的話,強烈建議你用 vue-cli 3 直接生成項目目錄,這樣會少挺多配置,好比配 tsconfig 什麼的,在用 vue-cli 的時候選擇 TypeScript 就好。html
若是想本身體驗從 0 開始配置請參考這個文檔 TypeScript-Vue-Startervue
這裏不提初始配置,由於 vue-cli 已經默認配置好了,在根目錄下,有個 tsconfig.json 的文件,在 src 的文件夾下面,有 shims-tsx.d.ts, shims-vue.d.ts 文件git
shims-vue.d.ts 這個文件,主要用於 TypeScript 識別.vue 文件,Ts 默認並不支持導入 vue 文件,這個文件告訴 ts 導入.vue 文件都按VueConstructor<Vue>
處理,所以導入 vue 文件必須寫.vue 後綴,可是這樣一樣的也會形成,就算你寫的導入的 .vue 文件的路徑就算是錯的,靜態檢測也不會檢測到錯誤,若是你把鼠標放上面你會看到錯誤的路徑就是指向這個文件,由於你定義了這個模塊是全部 .vue 後綴的導入都會指向到這個文件,可是若是你的路徑是對的,ts 能讀出正確的 module。 說那麼多可能沒用,看一下下面兩張圖就懂了github
shims-tsx.d.ts 文件,這個文件主要是方便你使用在 ts 中使用 jsx 語法的,若是不使用 jsx 語法,能夠無視這個,可是強烈建議使用 jsx 語法,畢竟模板是無法得到靜態類型提示的,固然,若是你境界高的話,直接用 vue render function。想要使用 jsx 語法的話,配合 babel-plugin-jsx-v-model,這個插件,體驗更佳,這個插件在 jsx 語法中實現了 v-model。vue-cli
推薦使用 Visual Studio Code 和 Vetur 插件,若是用的是 IDE,推薦使用 WebStormtypescript
Vue.extend()
和 vue-class-component
Vue.extend()
:使用基礎 Vue 構造器,建立一個「子類」。 這種方式最接近 Vue 的單文件組件的寫法,若是一個完善 Vue 項目從 JS 改爲 TS,用這種方法很快,只要加上 lang=ts
和一些必要的變量類型就行了,而後用Vue.extend()
包裹就好。請看下面的例子:
JavaScript
json
vue-class-component
:一般和vue-property-decorator
一塊兒搭配使用,實際使用只使用vue-property-decorator
就行了,vue-property-decorator
是在vue-class-component
上擴展來的,而且提供了不少修飾器好比 @Prop
和@Watch
等等,使用這個能夠編寫類式組件,可是若是你是完善的項目 JS 改 TS 的話,須要改的地方不少。看一下下面的例子:
JavaScript
segmentfault
組件 props數組
Vue.extend()
實現 props 其實和 JavaScript 沒有任何差異,可是若是你須要有 Object 變量類型提示,那就有點不同了babel
vue-class-component
實現 props, 須要從 vue-property-decorator 引入 Prop 這個修飾符,使用起來也很是方便,仍是用上面的例子
// JavaScript
props: ['isVisible', 'title', 'item', 'count', 'items']
// 若是加入了prop驗證,這樣寫
props: {
isVisible: {
type: Boolean,
required: true
},
title: {
type: [String, Number]
},
item: {
type: Object
},
items: {
type: Array,
}
count: {
count: Number
}
}
// TypeScript
/* 這種寫法沒有任何改變,可是這樣沒有任何類型提示,這些變量能被TS識別,可是會所有被識別成any,和沒類型檢查同樣 */
props: ['isVisible', 'title', 'item', 'count', 'items']
/* 當加入prop驗證以後,TS就會提示prop類型了,若是是對象的話,還能有對象的成員提示,寫法和JS寫法差很少,只是對象類型(包括對象,數組和函數)的有點差異,這樣寫的話。*/
// 假設item對象的結構是
interface Item {
key: string
val: string
num: number
}
props: {
isVisible: {
type: Boolean,
required: true
},
title: {
type: [String, Number]
},
item: {
// 注意這裏不是
// Object as Item
type: Object as () => Item
},
itmes: {
// 注意這裏不是
// Array as Array<Item>
type: Array as () => Array<Item>
}
count: {
count: Number
}
}
// vue-class-component方式
import { Vue, Component, Prop } from 'vue-property-decorator'
// 注意要加非空斷言符 ! 否則會報,固然,你定義成any類型當我沒說
/* [non-null-assertion-operator](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#non-null-assertion-operator) 關於非空斷言能夠參考這個 */
@Prop({
required: true
}) isVisible!: boolean
@Prop() title!: string | number
@Prop() item!: Item
@Prop() items!: Array<Item>
@Prop() count!: number
複製代碼
組件 data computed methods watch
Vue.extend()
實現 data 其實和 JavaScript 沒有任何差異,computed 的話,也沒什麼大的改變,可是有 this 參與運算的必須標明返回值類型,否則會報錯, methods 的處理方式和 computed 的同樣,有 this 參與運算的必須標明返回值類型,watch 也是同樣
vue-class-component
實現 data 的話,直接在類裏面寫變量就好,computed 的話,寫法相似 getter 和 setter,methods 處理方式就是直接在裏面寫方法,watch 須要從 vue-property-decorator 引入 Watch 這個修飾符
//一個簡單的例子
// Vue.extend()
import Vue from 'vue'
export default Vue.extend({
data() {
return {
count: 1,
item: {
c: '',
n: ''
}
}
},
computed: {
// 須要標註有 `this` 參與運算的返回值類型
num(): number {
return this.count
},
name: {
// 須要標註有 `this` 參與運算的返回值類型
get(): string {
return this.item.n
},
set(val: string) {
this.item.n = val
}
}
},
watch: {
count(newVal: number, oldVal: number): void {
console.log(newVal)
},
'item.n'(newVal: string, oldVal: string): void {
console.log(newVal)
},
item: {
handler(newV, oldVal) {
console.log(oldVal)
},
deep: true
}
},
methods: {
reset(): void {
this.$emit('reset')
},
getKey(): string {
return this.item.c
}
}
})
// vue-class-component
import { Vue, Component, Watch } from 'vue-property-decorator'
interface KeyValue {
c: string
n: string
}
@Component
export default class Test extends Vue {
// data
count: number = 1
item: KeyValue = {
c: '',
n: ''
}
// computed
get num(): number {
return this.count
}
get name(): string {
return this.item.n
}
// 注意,這裏不能標返回值類型,就算寫void也不行
set name(val: string) {
this.item.n = val
}
// watch
@Watch('count')
watchCount(newVal: number, oldVal: number): void {
console.log(newVal)
}
@Watch('item.n')
watchName(newVal: string, oldVal: string): void {
console.log(newVal)
}
@Watch('item', { deep: true })
watchItem(newVal: KeyValue, oldVal: KeyValue): void {
console.log(newVal)
}
// methods
reset(): void {
this.$emit('reset')
},
getKey(): string {
return this.item.c
}
}
複製代碼
組件 components
Vue.extend()
components 和 JavaScript 寫法徹底一致
vue-class-component
須要把導入的組件寫在修飾器@Components({})裏面
// Vue.extend
import Vue from 'vue'
import MainHeader from './header.vue'
import MainContent from './content.vue'
export default Vue.extend({
components: {
MainHeader,
MainContent
}
})
// vue-class-component
import { Vue, Component } from 'vue-property-decorator'
import MainHeader from './header.vue'
import MainContent from './content.vue'
@Component({
components: {
MainHeader,
MainContent
}
})
export default class extends Vue {}
複製代碼
組件 mixins
Vue.extend()
並不能徹底實現 mixins 多混入的效果,只能混入一個。不推薦混入用這種方式寫,沒法實現多繼承。若是你非要嘗試這種寫法,能夠看看這個Issue,我沒有嘗試過這種寫法,不過有人寫了個例子,能夠做爲參考,可是我嘗試了沒成功
// ExampleMixin.vue
export default Vue.extend({
data () {
return {
testValue: 'test'
}
}
})
// other.vue
export default Vue.extend({
mixins: [ExampleMixin],
created () {
this.testValue // error, testValue 不存在!
}
})
咱們須要稍做修改:
// other.vue
export default ExampleMixin.extend({
mixins: [ExampleMixin],
created () {
this.testValue // 編譯經過
}
})
複製代碼
vue-class-component
可以實現多混入,寫法相似類繼承
// mixin1.ts
import Vue from 'vue'
export default Vue.extend({
data () {
return {
valFromMixin1: 'test'
}
}
})
// 不能是
// 這種寫法會報 Mixin1 is not a constructor function type
export default {
data () {
return {
valFromMixin1: 'test'
}
}
}
// mixin2.ts
import { Component, Vue } from 'vue-property-decorator'
@Component
export default class Mixin2 extends Vue {
methodFromMixin2() {}
}
// test.ts
import Mixin1 from './mixin1'
import Mixin2 from './mixin2'
import { Component, Mixins } from 'vue-property-decorator'
export default class Test extends Mixins(Mixin1, Mixin2) {
test() {
this.methodFromMixin2()
console.log(this.valFromMixin1)
}
}
// 若是隻混入一個的話,能夠這樣寫
export default class Test extends Mixin1 {}
export default class Test extends Mixin2 {}
複製代碼
這樣寫不只不會報錯,並且編輯器還有提示
這張圖能夠看出,setWatch 是 BookingWatcher 裏面的方法,實際上,Test 這個類並無自身的屬性,都是從 Vue,BookingWatcher 還有 TestMixins 繼承過來的
函數式組件
這個只能用 Vue.extends(),Vue-class-component 無能爲力,能夠看看這個 Issue
這裏提供一個函數式組件TypeScript的寫法
在 Vue 中使用 TypeScript 的一些思考(實踐)
vue 官網 TypeScript 支持
$refs 報錯
$refs 報錯這個問題相信基本都遇到,除非你真沒用到這個,如圖:
報錯信息是 Property 'blur' does not exist on type 'Vue | Element | Vue[] | Element[]'. Property 'blur' does not exist on type 'Vue'.
解決方案:把上圖報錯部分改爲
// 把這個變量改爲定義成HTMLInputElement就好,這裏須要用到類型斷言
test() {
let inputBox: HTMLInputElement = this.$refs.inputBox as HTMLInputElement
inputBox.blur()
}
複製代碼
若是引用的比較多,而且引用裏面有本身的組件的話,能夠這樣寫:
$refs!: {}
這樣編輯器還會提示組件裏面有什麼方法,當你打出 this.$refs.header.時候,編輯器有提示 Header 這個組件裏面的屬性和方法