詳解Vue2.5+遷移至Typescript指南
為什么要遷移至Typescript
Javascript本身是動態(tài)弱類型的語言,這樣的特點導致了Javascript代碼中充斥著很多Uncaught TypeError的報錯,給開發(fā)調(diào)試和線上代碼穩(wěn)定都帶來了不小的負面影響。
而Typescript提供了靜態(tài)類型檢查,使很多類型錯誤在編寫時就已經(jīng)發(fā)現(xiàn),不會帶到測試階段。
同時,Javascript不定義model就可以使用一個對象,有人喜歡這樣的靈活性,的確這樣的語法在model不復雜的時候可以快速的開發(fā)出需要的功能,但一旦model龐大,找一個需要的屬性值都不知道從何找起。而在Typescript中,我們需要使用TS中的interface type等方式先定義出model,才可以調(diào)用其屬性值,所以Typescript極大的提高了代碼的可讀性。
可行性
因為TypeScript是JavaScript的超集,TypeScript 不會阻止 JavaScript 的運行,即使存在類型錯誤也不例外,這能讓你的 JavaScript 逐步遷移至 TypeScript。所以可以慢慢地做遷移,一次遷移一個模塊,選擇一個模塊,重命名.js文件到.ts,在代碼中添加類型注釋。當你完成這個模塊時,選擇下一個模塊。
如何將已有的Vue項目遷移至Typescript
安裝依賴
Vue官方提供了一個庫Vue-class-component,用于讓我們使用Ts的類聲明方式來編寫vue組件代碼。Vue-property-decorator則是在Vue-class-component的基礎上提供了裝飾器的方式來編寫代碼。首先我們需要在package.json中引入這兩個依賴。
我的項目是基于vue-cli@3.X創(chuàng)建的,還需要在項目中引入@vue/cli-plugin-typescript typescript兩個依賴來完成Typescript的編譯。
配置tsconfig.json
在項目根目錄新建tsconfig.json,并引入以下代碼
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"noFallthroughCasesInSwitch":true,
"noImplicitAny":true,
"noImplicitReturns":true,
"noImplicitThis":true,
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"./app/common/*"
],
"_app/*": [
"./app/*"
],
"_c/*": [
"./app/common/components/*"
],
"api/*": [
"./app/service/*"
],
"assets/*": [
"./app/assets/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
],
},
"include": [ // 在此出填寫你的項目中需要按照typescript編譯的文件路徑
"app/**/*.ts",
"app/**/*.tsx",
"app/**/*.d.ts",
"app/**/*.vue",
],
"exclude": [
"node_modules"
]
}
特別需要注意的是,現(xiàn)在的vue項目中大多使用了webpack的alias來解析路徑,在tsconfig.json中需要配置path屬性,讓typescript同樣認識在webpack中配置的路徑別名。
添加全局聲明文件
因為在ts文件中是無法識別vue文件的,所以需要在項目根目錄新建shims-vue.d.ts文件,添加以下代碼,來讓ts識別vue文件。
import Vue from 'vue';
declare module '*.vue' {
export default Vue;
}
由下而上的遷移
因為是遷移已經(jīng)存在的項目,不建議開始就把main.js重命名為main.ts,對于絕大多數(shù)Vue項目,main.js引入了太多的依賴,我們應該首先從依賴著手,自下而上的遷移Typescript。對于項目中一些偏底層,甚至是框架維護者所提供的庫函數(shù),我們不關心其實現(xiàn)邏輯,所以沒有必要將其改寫為ts文件,只需要給其加聲明文件供我們的業(yè)務代碼調(diào)用即可。
在我的項目中,service層的邏輯非常簡單,僅僅是傳參數(shù)調(diào)用接口,沒有添加任何其他的邏輯,邏輯如此簡單其實沒有什么必要改寫為ts文件,所以我為service層的文件編寫聲明文件,來為調(diào)用service層的代碼提供類型聲明。
聲明文件編寫方法
一個js文件如下
//service.js
import axios from '@/libs/api.request'
export default {
/**
* 創(chuàng)建賬戶
* @param {Object} data
* @param {String} data.accountType optinal
* @param {String} data.username
* @param {String} data.password
* @param {String} data.gender X | F | M
* @param {String} data.email
* @param {Number} data.level
*/
createAccount(data) {
return axios.request({
url: `/api/account/createUser`,
method: 'post',
data: data
}).then((res) => [res, null]).catch((err) => [null, err]);
},
}
可以看到,在使用typescript之前,對于一個函數(shù)的參數(shù)和返回值等信息的提示是通過jsdoc實現(xiàn)的,能夠在調(diào)用時確定參數(shù)類型及名稱,但jsdoc畢竟只是注釋,并不能提供類型校驗,所以在這里我們?yōu)槠渚帉懧暶魑募帉懞蟮穆暶魑募缦?/p>
//service.d.ts
interface createAccountParams {
accountType?: string,
username: string,
password: string,
gender: 'X' | 'F' | 'M',
email: string,
level?: number
}
interface createAccountReturn {
userId: string,
}
export interface Service {
createAccount(data: createAccountParams): createAccountReturn
}
這樣一個service層的接口文件的聲明文件就編寫完成了,為了獲得Typescript和vscode提供的類型提示和校驗,在main.js中將service.js導出的實例綁定在了Vue原型上,使得我們可以在vue組件中通過vm.$service方便的訪問service實例。但是Typescript并不知道Vue實例上有什么屬性,這時需要我們在之前添加的shims-vue.d.ts文件中添加幾行代碼。
import Vue from 'vue';
import Service from "pathToService/service.d.ts";
declare module '*.vue' {
export default Vue;
}
declare module "vue/types/vue" {
interface Vue {
$service: Service
}
}
得力于typescript中提供的模塊補充功能,我們可以在node_modules/vue/types/vue中補充我們需要在Vue上提供的屬性。
改寫Vue文件
我們需要將原來的vue文件改寫成使用vue-property-decorator編寫的方式。
<script lang="ts">
import {Component,Vue} from "vue-property-decorator";
@Component
export default class MyComponent extends Vue{
// 聲明data
form: {
accountType?: string,
username: string,
password: string,
gender: 'X' | 'F' | 'M',
email: string,
level?: number
} = {
username:'',
password:'',
gender:'X',
email:''
};
// 聲明methods
async submit(): void {
//調(diào)用上面的service接口
const [res,err] = await this.$service.createAccount(this.form);
}
}
</script>
至此一個Vue項目遷移至Typescript的過程就已經(jīng)完成了,剩下的工作就是將代碼中其他的文件一步步由js遷移到typescript中。
把方法綁定到Vue實例下
除了我們之前提到過的將自己編寫的service掛載到vue實例上,大家一定清楚在vue項目中,我們經(jīng)常會調(diào)用this.$refs this.$router this.$store等等,typescript也會檢查這些屬性是否被綁定在了vue實例上,那么我們并沒有在類型系統(tǒng)中聲明這些值,按道理應該報Property '$refs' does not exist on type [your_component_name]
真相是vue vue-router vuex都已經(jīng)給我們聲明了相應的類型,我們可以cd ./node_modules/vue/types/目錄中去查看
截取少量代碼如下所示:
export interface Vue {
readonly $el: Element;
readonly $options: ComponentOptions<Vue>;
readonly $parent: Vue;
readonly $root: Vue;
readonly $children: Vue[];
readonly $refs: { [key: string]: Vue | Element | Vue[] | Element[] };
readonly $slots: { [key: string]: VNode[] | undefined };
readonly $scopedSlots: { [key: string]: NormalizedScopedSlot | undefined };
readonly $isServer: boolean;
readonly $data: Record<string, any>;
readonly $props: Record<string, any>;
readonly $ssrContext: any;
readonly $vnode: VNode;
readonly $attrs: Record<string, string>;
readonly $listeners: Record<string, Function | Function[]>;
}
只要正常的在依賴中安裝了vue-router vuex就已經(jīng)通過模塊補充的方式將類型添加到了vue實例上。
在一些項目中,vue-router vuex這些依賴不是通過安裝在依賴中引入的,而是通過index.html引入的cdn資源文件,這樣在開發(fā)過程中我們就無法獲取其類型。
這個時候我們可以通過安裝@types依賴的方式將類型系統(tǒng)補充到項目中,如npm install @types/jquery --save-dev。
不幸的是vue-router和vuex的types包已經(jīng)廢棄了,只能通過手動去github上下載對應版本的vue-router vuex將types文件引入到項目中,你可以像我一樣在項目中新建一個types目錄,引入需要的類型聲明文件。
這樣就可以直接在vue實例上訪問到$store $router等屬性了。
同理當你想要引入其他的組件庫上的一些類型文件時,也是這樣的方式。
一些需要注意的問題
在vue開發(fā)過程中我們會使用this.$refs去訪問某一個具體實例的方法,但是這在ts中是訪問不到的常見的,比如要想要使用form組件中的validate方法,我們需要給其加上類型斷言
this.$refs.form.validate()變?yōu)?code>(this.$refs.form as Vue & {validate:Function}).validate()
來告訴編譯器this.$refs.form上有validate方法。
因為類型斷言前提條件是是當 S 類型是 T 類型的子集,或者 T 類型是 S 類型的子集時,S 能被成功斷言成 T,所以需要在類型斷言時合并Vue類型。
同時也可以通過vue-property-decorator提供給我們的裝飾器一勞永逸的將該refs添加到computed屬性上
import { Vue, Component, Ref } from 'vue-property-decorator'
import Form from '@/path/to/another-component.vue'
@Component
export default class YourComponent extends Vue {
@Ref() readonly form!: Form
}
等同于
export default {
computed() {
form: {
cache: false,
get() {
return this.$refs.form as Form
}
},
}
}
這樣我們就可以通過 this.form.validate()來做表單校驗了
新手容易遇到的一些問題
疑問1:interface和type有什么區(qū)別?
type 可以聲明基本類型別名,聯(lián)合類型,元組等類型
eg.type a = string;是被允許的,
interface 會自動聲明合并
interface person{
gender:string
age:number
}
interface person{
name:string
}
疑問2: 錯誤 Property 'hideContent' has no initializer and is not definitely assigned in the constructor.
strictPropertyInitialization屬性會在strict設置為true時自動被設置為true。
但這個屬性并不合理,它要求每個實例的屬性都有初始值,我們在tsconfig將其設置為false就好了。
疑問3: 賦值兼容性
interface person {
name:string
age:number
}
interface student {
name:string
age:number
stuid:string
}
let person: person= {
name:'name',
age:1
}
let student: student = {
name:'name',
age:1,
stuid:'stuid'
};
person = student //這樣是可以的
student = person //這樣不允許
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
淺談vue項目4rs vue-router上線后history模式遇到的坑
今天小編就為大家分享一篇淺談vue項目4rs vue-router上線后history模式遇到的坑,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09
vue?循環(huán)動態(tài)設置ref并獲取$refs方式
這篇文章主要介紹了vue?循環(huán)動態(tài)設置ref并獲取$refs方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01
基于ant-design-vue實現(xiàn)表格操作按鈕組件
這篇文章主要為大家介紹了基于ant-design-vue實現(xiàn)表格操作按鈕組件示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06
Vue2.X和Vue3.0數(shù)據(jù)響應原理變化的區(qū)別
這篇文章主要介紹了Vue2.X和Vue3.0數(shù)據(jù)響應原理變化的區(qū)別,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-11-11
element-ui 表格實現(xiàn)單元格可編輯的示例
下面小編就為大家分享一篇element-ui 表格實現(xiàn)單元格可編輯的示例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-02-02

