Vue 中使用 typescript
什么是typescript
typescript 為 javaScript的超集,這意味着它支持所有都JavaScript都語法。它很像JavaScript都強類型版本,除此之外,它還有一些擴展的語法,如interface/module等。
typescript 在編譯期會去掉類型和特有語法,生成純粹的JavaScript。
Typescript 5年內的熱度隨時間變化的趨勢,整體呈現一個上升的趨勢。也說明ts越來越️受大家的關注了。
安裝typescript
npm install -g typescript
tsc greeter.ts
舉個栗子
左右對比可以看出typescript 在編譯期會去掉類型和特有語法,生成純粹的JavaScript。
greeter.ts
interface Person {
firstName: string;
lastName: string;
}
function greeter(person: Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
let user = { firstName: "Jane", lastName: "User" };
greeter.js
function greeter(person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
var user = { firstName: "Jane", lastName: "User" };
為什么需要使用它?
優勢:
- 靜態類型檢查
- IDE 智能提示
- 代碼重構
- 可讀性
1. 靜態類型檢查
靜態類型檢查首要優點就是能盡早的發現邏輯錯誤,而不是上線之后才發現。
1.1 類型分析
傳參過程字段錯誤,或類型錯誤使用。(進行參數標注后,在編碼過程中即可檢查出錯誤。)
1.2 類型推斷:函數的返回值可通過ts類型推斷得出.這一步驟是 在編譯時進行
在編譯時進行類型分析
example:
eg1: 我在使用ts寫vue-router 的 動態路徑參數時就發現了一個問題, 動態路徑參數 以冒號開頭 path: '/user/:id',我們會誤認為id為一個number,如果使用ts你將得到提示 我們應該傳入一個string類型的id. 傳入一個number類型的id可能並不會出錯,js會對它進行隱式類型轉換,但是傳入一個string會使它更安全和規范.
eg2: 個人使用后的效果
interface Person {
firstName: string;
lastName: string;
}
function greeter(person: Person): string {
return "Hello, " + person.firstName + " " + person.lastName;
}
let user = { firstName: 1223, lastname: "User" };
greeter(user);
2.智能補全
在編寫代碼時ide就會提示函數簽名.
interface Person {
firstName: string;
lastName: string;
}
/**
* 問候語句
* @param {Person} person
* @returns {string}
*/
function greeter(person: Person): string {
return "Hello, " + person.firstName + " " + person.lastName;
}
/**
* hello word!
*
* @param {string} word
* @returns {string}
*/
function Hello(word: string): string {
return "hello," + word;
}
export { greeter, Hello };
直接將這個ts文件引入到其他ts文件中,不僅補全了所有的參數類型,還告訴你需要填入一個參數,並且你只有填入一個Person類型的對象才不會報錯。(智能補全和參數校驗)
3.在重構上
動態一時爽,重構火葬場.
typescript 在重構上的優勢,我們主要從三方面說明。
- 重命名符號,可將一切引用的地方都進行修改。
在vs code 中如果我們想修改函數、變量或者類的名稱,我們可以使用重命名符號的功能,在當前項目中正確的修改所有的引用.這個既可以在ts中使用,也可以在js中使用,而它的底層實現都是依靠ts 的語法分析器實現的。 - 自動更新引用路徑(vs code)。
在重構的過程中,我們可能需要移動文件的路徑,這往往會導致其他地方的import失效,這時候vs code提供了自動更新引用路徑的功能。它的底層實現也是依靠ts 的語法分析器實現的。 - 校驗函數簽名。
有時候我們會重構類或函數的簽名,如果有引用到的地方忘記修改,除了運行時候能發現,其他時候往往難以察覺,且 ESLint 也只能是排查簡單的問題,所以出了BUG會非常麻煩。 而 TypeScript 不一樣,在編碼時就能及時的發現哪里錯了,哪里應該改動但沒有修改。
[函數簽名 MDN][5]
4. 可讀性
可讀性上,TypeScript 明顯占優,查看開源代碼時,如果注釋不是很完善,往往會看的雲里霧里,而 TypeScript 在同等條件下,至少有個類型,能讓自己更容易明白代碼的參數、返回值和意圖。
TS+Vue初探
配置
在正式開發之前,我們需要了解一些基本的配置。
1.tsconfig.json 是 ts 項目的編譯選項配置文件. 在 ts 項目中如果你不添加這份文件,ts 會使用默認的配置. 掃描二維碼獲取配置項目。
2. ts-loader:Webpack 的TypeScript 加載器,就是為了讓 webpack 編譯 .ts .tsx文件。
3. TSLint:.ts .tsx文件的代碼風格檢查工具。(作用類似於ESLint)
4. vue-shim.d.ts:由於 TypeScript 默認並不支持 *.vue 后綴的文件,所以在 vue 項目中引入的時候需要創建一個 vue-shim.d.ts 文件,放在項目根目錄下,例如 src/vue-shim.d.ts。
在Vue里面寫TS的方式
圖中內容應寫在script中的lang="ts" 。
上圖:使用 Vue.extend的基礎用法。
下圖:基於類的Vue組件
通過對比我們發現,上面的編碼方式更接近我們平時JS的寫法,但是我們並不能感受到ts的類型檢查和類型推斷。
下面張圖的寫法更有助於我們得到ts的類型檢查。這需要我們引入 vue-class-component ,雖然template中還是不能得到補全,但是script 中的內容得到了更好的補全。 下面我們了解一下vue-class-component的作用。
vue-class-component & vue-property-decorator
vue-class-component 強化 Vue 組件,使用裝飾器語法使 Vue 組件更好的跟TS結合使用。
vue-property-decorator在 vue-class-component 的基礎上增加了更多與 Vue 相關的裝飾器,使Vue組件更好的跟TS結合使用。
這兩者都是離不開裝飾器的,(decorator)裝飾器已在ES提案中。Decorator是裝飾器模式的實踐。裝飾器模式呢,它是繼承關系的一個替代方案。動態地給對象添加額外的職責。在不改變接口的前提下,增強類的性能。下面我們以 鋼鐵俠 為例講解如何使用 ES7 的 decorator。
以鋼鐵俠為例,鋼鐵俠本質是一個人,只是“裝飾”了很多武器方才變得那么 NB,不過再怎么裝飾他還是一個人,它本質是沒有被改變的。所以,裝飾器沒有改變其繼承關系,但也同樣能夠為它添加很多厲害的技能。簡單的說,裝飾器是在不修改一個類的繼承關系的前提下,為一個類修改或添加成員。
裝飾器主要接收的三個參數:
target 要在其上定義屬性的對象。
key 要定義或修改的屬性的名稱。
descriptor 將被定義或修改的屬性描述符。
下面我們通過代碼中我們為一個人添加了飛行的功能:
typescript VS JavaScript
了解了上面的基礎知識,現在我將同一段代碼分別使用js 和 ts來書寫,現在我們來對比他們之間的差別。
- Props (Properties)
使用js,我們有很多中方式來定義組件的 Props,但是大多都摻雜了 Vue 的私有特征,與 ES 格格不入,例如左邊的代碼,明明我們是把這個對象的 prop 屬性定義成為了一個包含兩個 string 元素的對象,但是我們卻可以直接通過這個對象來訪問 "name" 字段,這很明顯是不符合 ES 語義的。
再來看看右邊的 TS 選手,通過 Prop 裝飾器把指定的字段標記為了 Prop,既保留了 ES 語法的語義,而且還能與 Vue 完美的配合,更棒的是,我們可以在編碼的過程中享受 TS 對 Prop 字段的靜態類型檢查。 - Method 和 data
再來看看 Method,JS 中定義 method 還是有我們上面提到的那個不符合 ES 語義的毛病。而在 TS 中,method 不需要額外的裝飾器——實例方法就會自動成為 Vue 組件的 method。類似的還有 data ,使用 TS 的語法,實例字段即可自動成為 Vue 組件的 data。 - Computed
在傳統的使用 JS 編寫的 Vue 代碼中,如果要定義計算屬性,我們需要在 computed 屬性中定義相應的函數。而這在 ES 中其實早就已經有了對應語義的語法——getter,所以在使用了 vue-class-component 的 vue 組件中,我們可以直接使用 getter 來定義計算屬性,不管是在語法上還是在語義上,相比普通的 JS 都略勝一籌
總結:我們使用vue-class-component讓vue組件的定義更加符合ES語義,使得TS能夠更好的進行語法分析,並基於此進行類型檢查。
業務場景中使用TS + Vue
1. 在Vue項目中定義data和props
這樣的寫法,讓我產生了兩個疑惑。
1.1 為什么使用 的寫法
@Prop(Number!😃 propA!: number
而不是
@Prop(Number) propA: number
1.2 為什么Prop需要前后寫兩次類型?
自我自答環節:
答1.1:因為我們定一個Phone這個類,沒有對phone、condition這些字段通過constructor進行初始化,所以需要在屬性上使用 顯式賦值斷言來幫助類型系統識別類型,這樣能夠讓屬性會被間接地初始化。
答1.2:前面括號里面的類型標注是Vue提供的類型檢查。冒號后面的是為TS提供的類型標注。
2. 編寫一個函數
這里我們將使用到js的接口,它經常用於定義復雜的參數類型和返回值類型。
上面的例子,我們需要為opts這個參數定義類型,方便后面編碼時的使用,這時我們就需要編寫一個接口來對它進行類型定義。這個接口里面包括三個必須屬性和一個可選屬性。必須屬性在對象初始化的時候必須賦值,但是有時候某個對象中的屬性可以被忽略的,不一定會被需要。我們可以將它設置為可選項。
隨着業務的發展,一些參數的字段會變的越來越多,越來越復雜。可能你想有沒有什么一勞永逸的方法,讓接口更加簡單,甚至讓它自動的適應業務的變化。於是我想出了這樣的代碼:
上面的代碼: 定義了一個鍵為string,他的值為number或string類型的接口,並且該接口的所有字段都是可選的,你甚至可以傳入一個空對象。所以我們可以使用上面的接口可以代替下面的接口,但是反之不行.
然而我們不應該去繞開這些檢查,因為這樣ts就不會為你檢查使用接口的對象應該存在那些屬性或者方法了。使用ts的意義就被大大減弱了。
3. 編寫第三方依賴
在日常的開發過程中,我們經常會遇到將第三方依賴引入到 TS 項目中沒有類型檢查的問題,這往往是因為這些項目沒有提供類型定義文件。這個時候,我們可以手動為這些第三方庫編寫類型定義文件,類型定義文件在編譯后會被完全忽略,所以並不會對現有代碼產生影響。以上面這個較為復雜的函數為例,它的作用是將傳入的所有的參數的所有字段合並到一個新的對象中並返回,盡管他的功能比較簡單,但是為它編寫類型定義還是需要一些 TS 的技巧。
1. 外部模塊聲明: 首先我們需要創建一個拓展名為 .d.ts 的文件,並在其中聲明一個模塊,聲明的模塊名稱需要跟我們在其他文件中引入的路徑相同。
2. 類型參數——泛型:首先讓我們考慮最簡單的情況,當傳入一個參數的時候,extend 函數應該返回與參數類型相同的對象,但是我們在編寫函數的時候並不知道用戶會傳入何種類型的參數,所以我們可以定義一個類型參數 T1,這時,extend 就被稱為泛型函數,T1 也被稱做泛型參數。在上面的例子中,extend 函數接受一個類型為 T1 參數並返回一個類型為 T1 的值,T1 需要用戶手動傳入,還好 TS 足夠聰明,在絕大多數情況下,TS 可以根據參數類型來自動推斷類型參數,免去了我們輸入類型參數的繁瑣步驟。只接受一個參數的 extend 函數並沒有很復雜,我們可以繼續考慮一些更復雜的情況。
這次讓我們定義一個接受三個參數的 extend 函數,同時它也接受三個泛型參數,而它返回值類型呢,則是 T1, T2, T3 的交叉類型。交叉類型讓我們可以把現有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性,相當於對這些類型的成員求並集。 例如, T1 & T2 & T3 這個類型的對象同時擁有了這三種類型的成員。在這里,交叉類型就滿足了我們對 extend 函數返回值類型的要求。值得注意的是,實際的 extend 函數可以接受不定個數的參數,也就是說,我們為它編寫的類型定義也需要同時兼容接受不定個數參數的情況,這就需要 TS 提供的函數重載功能。
3. 重載:在大多數靜態類型編程語言中,編譯器允許存在參數類型、個數不同的多個同名函數,這個特性被稱為函數重載。TS 支持函數重載的特性,所以我們可以定義多個接受不同數量參數的 extend 方法,在用戶調用時,TS 會自動的在這些同名函數中選擇正確的重載定義。有了函數重載的幫助,我們可以在使用 extend 的大多數場景下享受到類型檢查的好處,只有在參數個數超過4個的時候,TS 才無法推斷出返回值類型。需要注意的是在 JS 中,運行時並不提供函數重載的能力,我們無法定義多個同名函數,即使他們接受的參數數量並不相同,為了實現函數重載的效果,開發人員需要手動在單個函數中對參數的類型、數量做出判斷。
到這里我們的第三方聲明就完成了,即使一個簡單函數的第三方聲明,我們也運用了很多ts的相關知識。
個人感受
前面我們講解了,使用在vue中使用ts能帶給我們的種種便利,現在就我個人感受而言,說一下美中不足的地方。
1.即使使用了ts,template 部分仍沒有靜態類型檢查和IDE智能提示,但官方成員表示在以后的 Vue 單文件中會提供這項功能。
2.將 Vue 單文件組件引入 TS 文件中,無法正確的提示其文件位置。
3.Vue 周邊工具,比如 Vuex,它對ts的支持薄弱,大量的功能難以直接遷移到ts中,並且沒有好的官方支持的方案。
4.毫無疑問,使用 TS 進行開發,相比於 JS ,我們需要花費更多的時間和精力。