Vue2.5+遷移至Typescript指南

Vue2.5+遷移至Typescript指南

爲何要遷移至Typescript

  • Javascript自己是動態弱類型的語言,這樣的特色致使了Javascript代碼中充斥着不少Uncaught TypeError的報錯,給開發調試和線上代碼穩定都帶來了不小的負面影響。
  • Typescript提供了靜態類型檢查,使不少類型錯誤在編寫時就已經發現,不會帶到測試階段。
  • 同時,Javascript不定義model就可使用一個對象,有人喜歡這樣的靈活性,的確這樣的語法在model不復雜的時候能夠快速的開發出須要的功能,但一旦model龐大,找一個須要的屬性值都不知道從何找起。而在Typescript中,咱們須要使用TS中的interface type等方式先定義出model,才能夠調用其屬性值,因此Typescript極大的提升了代碼的可讀性。

可行性

由於TypeScript是JavaScript的超集,TypeScript 不會阻止 JavaScript 的運行,即便存在類型錯誤也不例外,這能讓你的 JavaScript 逐步遷移至 TypeScript。因此能夠慢慢地作遷移,一次遷移一個模塊,選擇一個模塊,重命名.js文件到.ts,在代碼中添加類型註釋。當你完成這個模塊時,選擇下一個模塊。html

如何將已有的Vue項目遷移至Typescript

安裝依賴

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

在項目根目錄新建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項目中大多使用了webpackalias來解析路徑,在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文件改寫成使用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中。

把方法綁定到Vue實例下

除了咱們以前提到過的將本身編寫的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-routervuex的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()來作表單校驗了

新手容易遇到的一些問題

疑問1:interface和type有什麼區別?

type 能夠聲明基本類型別名,聯合類型,元組等類型

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  //這樣不容許
未完待續,歡迎補充
相關文章
相關標籤/搜索