Uncaught TypeError
的報錯,給開發調試和線上代碼穩定都帶來了不小的負面影響。interface type
等方式先定義出model,才能夠調用其屬性值,因此Typescript極大的提升了代碼的可讀性。由於TypeScript是JavaScript的超集,TypeScript 不會阻止 JavaScript 的運行,即便存在類型錯誤也不例外,這能讓你的 JavaScript 逐步遷移至 TypeScript。因此能夠慢慢地作遷移,一次遷移一個模塊,選擇一個模塊,重命名.js文件到.ts,在代碼中添加類型註釋。當你完成這個模塊時,選擇下一個模塊。html
Vue官方提供了一個庫Vue-class-component,用於讓咱們使用Ts的類聲明方式來編寫vue組件代碼。Vue-property-decorator則是在Vue-class-component的基礎上提供了裝飾器的方式來編寫代碼。首先咱們須要在package.json中引入這兩個依賴。vue
個人項目是基於vue-cli@3.X建立的,還須要在項目中引入@vue/cli-plugin-typescript
typescript
兩個依賴來完成Typescript的編譯。node
在項目根目錄新建tsconfig.json
,並引入如下代碼jquery
{ "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" ] }
特別須要注意的是,如今的vue項目中大多使用了webpack
的alias
來解析路徑,在tsconfig.json中須要配置path
屬性,讓typescript一樣認識在webpack中配置的路徑別名。webpack
由於在ts文件中是沒法識別vue文件的,因此須要在項目根目錄新建shims-vue.d.ts
文件,添加如下代碼,來讓ts識別vue文件。ios
import Vue from 'vue'; declare module '*.vue' { export default Vue; }
由於是遷移已經存在的項目,不建議開始就把main.js
重命名爲main.ts
,對於絕大多數Vue項目,main.js
引入了太多的依賴,咱們應該首先從依賴着手,自下而上的遷移Typescript。對於項目中一些偏底層,甚至是框架維護者所提供的庫函數,咱們不關心其實現邏輯,因此沒有必要將其改寫爲ts
文件,只須要給其加聲明文件
供咱們的業務代碼調用便可。git
在個人項目中,service層的邏輯很是簡單,僅僅是傳參數調用接口,沒有添加任何其餘的邏輯,邏輯如此簡單其實沒有什麼必要改寫爲ts文件,因此我爲service層的文件編寫聲明文件,來爲調用service層的代碼提供類型聲明。github
一個js文件以下web
//service.js import axios from '@/libs/api.request' export default { /** * 建立帳戶 * @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以前,對於一個函數的參數和返回值等信息的提示是經過jsdoc
實現的,可以在調用時肯定參數類型及名稱,但jsdoc
畢竟只是註釋,並不能提供類型校驗,因此在這裏咱們爲其編寫聲明文件,編寫後的聲明文件以下vue-router
//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層的接口文件的聲明文件就編寫完成了,咱們每每在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-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 { //調用上面的service接口 const [res,err] = await this.$service.createAccount(this.form); } } </script>
至此一個Vue項目遷移至Typescript的過程就已經完成了,剩下的工做就是將代碼中其餘的文件一步步由js遷移到typescript中。
除了咱們以前提到過的將本身編寫的service
掛載到vue實例上,你們必定清楚在vue項目中,咱們常常會調用this.$refs
this.$router
this.$store
等等,typescript也會檢查這些屬性是否被綁定在了vue實例上,那麼咱們並無在類型系統中聲明這些值,按道理應該報Property '$refs' does not exist on type [your_component_name]
真相是$refs
vue-router
vuex
都已經給咱們聲明瞭相應的類型,咱們能夠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
就已經經過模塊補充的方式將類型添加到了vue實例上。
在一些項目中,vue-router
vuex
這些依賴不是經過安裝在依賴中引入的,而是經過index.html引入的cdn資源文件,這樣在開發過程當中咱們就沒法獲取其類型。
這個時候咱們能夠經過安裝@types
依賴的方式將類型系統補充到項目中,如npm install @types/jquery --save-dev
。
不幸的是vue-router
和vuex
的types包已經廢棄了,只能經過手動去github上下載對應版本的vue-router
vuex
將types文件引入到項目中,你能夠像我同樣在項目中新建一個types目錄,引入須要的類型聲明文件。
這樣就能夠直接在vue實例上訪問到$store
$router
等屬性了。
同理當你想要引入其餘的組件庫上的一些類型文件時,也是這樣的方式。
在vue開發過程當中咱們會使用this.$refs
去訪問某一個具體實例的方法,可是這在ts中是訪問不到的
常見的,好比要想要使用form組件中的validate方法,咱們須要給其加上類型斷言this.$refs.form.validate()
變爲(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()
來作表單校驗了
type 能夠聲明基本類型別名,聯合類型,元組等類型
eg.type a = string;
是被容許的,
interface 會自動聲明合併
interface person{ gender:string age:number } interface person{ name:string }
strictPropertyInitialization
屬性會在strict
設置爲true
時自動被設置爲true
。
但這個屬性並不合理,它要求每一個實例的屬性都有初始值,咱們在tsconfig
將其設置爲false就行了。
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 //這樣不容許
未完待續,歡迎補充