在 2.5.0 版本中,Vue 大大改進了類型聲明系統以更好地使用默認的基於對象的 API。html
意味着當咱們僅是安裝 Vue 的聲明文件時,一切也都將會按預期進行:vue
this
,就是 Vue;this
屬性上,具備 Methods 選項上定義的同名函數屬性;this
屬性上;在這篇文章裏,咱們來談談上述背後的故事。git
當咱們建立 Vue 實例,並在 Methods 上定義方法時, this
不只具備 Vue 實例上屬性,同時也具備與 Methods 選項上同名的函數屬性:github
new Vue({
methods: {
test () {
this.$el // Vue 實例上的屬性
}
},
created () {
this.test() // methods 選項上同名的方法
this.$el // Vue 實例上的屬性
}
})
複製代碼
爲了探究其原理,咱們把組件選項的聲明改寫成如下方式:typescript
定義 Methods:app
// methods 是 [key: string]: (this: Vue) => any 的集合
type Methods = Record<string, (this: Vue) => any>
複製代碼
這會存在一個問題,Methods 上定義的方法裏的 this
,所有都是 Vue 構造函數上的方法,而不能訪問咱們自定義的方法。 咱們須要把 Vue 實例傳進去:函數
type Methods<V> = Record<string, (this: V) => any>
複製代碼
組件選項(一樣也須要傳實例):ui
interface ComponentOption<V> {
methods: Methods<V>,
created?(this: V): void
}
複製代碼
咱們可使用它:this
declare function testVue<V extends Vue>(option: ComponentOption<V>): V 複製代碼
此種情形下,咱們必須將組件實例的類型顯式傳入,從而使其編譯經過:spa
interface TestComponent extends Vue {
test (): void
}
testVue<TestComponent>({
methods: {
test () {}
},
created () {
this.test() // 編譯經過
this.$el // 經過
}
})
複製代碼
這有點麻煩,爲了使它能按咱們預期的工做,咱們定義了一個額外的 interface。
在 Vue 的聲明文件裏,使用了一種簡單的方式:經過使用 ThisType<T>
映射類型,讓 this
具備所須要的屬性。
在 TypeScript 倉庫 ThisType<T>
的 PR 下,有一個使用例子:
在這個例子中,經過對 methods 的值使用 ThisType<D & M>
,從而 TypeScript 推導出 methods 對象中 this
便是: { x: number, y: number } & { moveBy(dx: number, dy: number ): void }
。
與此相似,咱們可讓 this
具備 Methods 上定義的同名函數屬性:
type DefaultMethods<V> = Record<string, (this: V) => any>
interface ComponentOption<
V,
Methods = DefaultMethods<V>
> {
methods: Methods,
created?(): void
}
declare function testVue<V extends Vue, Methods> (
option: ComponentOption<V, Methods> & ThisType<V & Methods>
): V & Methods
testVue({
methods: {
test () {}
},
created () {
this.test() // 編譯經過
this.$el // 實例上的屬性
}
})
複製代碼
在上面代碼中,咱們:
[key: string]: (this: V) => any
的 Methods。ThisType
。ThisType<V & Methods>
標誌着實例內的 this
便是 V 與 Methods 的交叉類型。{ test (): void }
,從而在實例內 this
便是:Vue & { test (): void }
;得益於上文中的 ThisType<T>
,Data 的處理有點相似與 Methods,惟一不一樣之處 Data 可有兩種不一樣類型,Object 或者 Function。它的類型寫法以下:
type DefaultData<V> = object | ((this: V) => object)
複製代碼
一樣,咱們也把 ComponentOption 與 testVue 稍做修改
interface ComponentOption<
V,
Data = DefaultData<V>,
Methods = DefaultMethods<V>
> {
data: Data
methods?: Methods,
created?(): void
}
declare function testVue<V extends Vue, Data, Methods> ( option: ComponentOption<V, Data, Methods> & ThisType<V & Data & Methods> ): V & Data& Methods 複製代碼
當 Data 是 Object 時,它能正常工做:
testVue({
data: {
testData: ''
},
created () {
this.testData // 編譯經過
}
})
複製代碼
當咱們傳入 Function 時,它並不能:
TypeScript 推斷出 Data 是 (() => { testData: string })
,這並非指望的 { testData: string }
,咱們須要對函數參數 options 的類型作少量修改,當 Data 傳入爲函數時,取函數返回值:
declare function testVue<V extends Vue, Data, Method>(
option: ComponentOption<V, Data | (() => Data), Method> & ThisType<V & Data & Method>
): V & Data & Method
複製代碼
這時候編譯能夠經過:
testVue({
data () {
return {
testData: ''
}
},
created () {
this.testData // 編譯經過
}
})
複製代碼
Computed 的處理彷佛有點棘手:它與 Methods 不一樣,當咱們在 Methods 中定義了一個方法,this
也會含有相同名字的函數屬性,而在 Computed 中定義具備返回值的方法時,咱們指望 this
含有函數返回值的同名屬性。
舉個例子:
new Vue({
computed: {
testComputed () {
return ''
}
},
methods: {
testFunc () {}
},
created () {
this.testFunc() // testFunc 是一個函數
this.testComputed // testComputed 是 string,並非一個返回值爲 string 的函數
}
})
複製代碼
咱們須要一個映射類型,把定義在 Computed 內具備返回值的函數,映射爲 key 爲函數名,值爲函數返回值的新類型:
type Accessors<T> = {
[K in keyof T]: (() => T[K])
}
複製代碼
Accessors<T>
將會把類型 T,映射爲具備相同屬性名稱,值爲函數返回值的新類型,在類型推斷時,此過程相反。
接着,咱們補充上例:
// Computed 是一組 [key: string]: any 的集合
type DefaultComputed = Record<string, any>
interface ComponentOption<
V,
Data = DefaultData<V>,
Computed = DefaultComputed,
Methods = DefaultMethods<V>
> {
data?: Data,
computed?: Accessors<Computed>
methods?: Methods,
created?(): void
}
declare function testVue<V extends Vue, Data, Compted, Methods> (
option: ComponentOption<V, Data | (() => Data), Compted, Methods> & ThisType<V & Data & Compted & Methods>
): V & Data & Compted & Methods
testVue({
computed: {
testComputed () {
return ''
}
},
created () {
this.testComputed // string
}
})
複製代碼
當調用 testVue 時,咱們傳入一個屬性爲 testComputed () => ''
的 Computed,TypeScript 會嘗試將類型映射至 Accessors<T>
,從而推導出 Computed 便是 { testComputed: string }
。
此外,Computed 具備另外一個寫法:get 與 set 形式,咱們只須要把映射類型作相應補充便可:
interface ComputedOptions<T> {
get?(): T,
set?(value: T): void
}
type Accessors<T> = {
[K in keyof T]: (() => T[K]) | ComputedOptions<T[K]> } 複製代碼
在上篇文章在 Vue 中使用 TypeScript 的一些思考(實踐)中,咱們已經討論了 Prop 的推導,在此再也不贅述。
此篇文章是對 Vue typings 的一次簡單解讀,但願你們看得懂源碼時,不要忘記了 Vue typings,畢竟 Vue typings 纔是給程序行爲以提示和約束的關鍵。