天天看點

Vue + TypeScript 項目起手式

本文首發于 Guanngxu 的個人部落格:Vue 入門避坑——Vue + TypeScript 項目起手式

在此前我使用的前端架構是 Angular,使用過 TypeScript 後你就會讨厭 JS 了,我學習 Vue 時的最新版本是 2.5,相信大部分同學都不會認為 Vue 那樣又細又長的代碼很美觀吧,簡單看了一些網絡部落格後,我毅然決然引入了 TypeScript 進行開發,本文僅整理記錄我自己遇到的一些坑。

使用 Cli

腳手架是一個比較友善的工具,這裡需要注意的是

@vue/cli

vue-cli

是不一樣的,推薦使用

npm i -g @vue/cli

安裝。

安裝完成後,可以直接使用

vue create your-app

建立項目,你可以選擇使用預設配置亦或是自己手動選擇配置,按提示一步一步向下走即可,它會根據你的選擇自己建立比如

tsconfig.json

等等配置檔案。這裡推薦使用

less

開發樣式,

sass

老是在安裝的過程中出問題。

當然你也可以使

vue ui

指令啟動一個本地服務,它是一個 Vue 項目管理器,提供了一個可視化的頁面供你管理自己的項目,它的樣子如下圖所示,還是比較清新的。

Vue + TypeScript 項目起手式

使用 vue-property-decorator

Vue 官方維護了 vue-class-component 裝飾器,vue-property-decorator 則是在

vue-class-component

基礎上增強了更多結合

Vue

特性的裝飾器,它可以讓 Vue 元件文法在結合了 TypeScript 文法後變得更加扁平化。

截止本文時間,

vue-property-decorator

共提供了 11 個裝飾器和 1 個

Mixins

方法,下面用

@Prop

舉個例子,是不是看起來引起極度舒适。

import { Vue, Component, Prop } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
    @Prop(Number) readonly propA: number | undefined
    @Prop({ default: 'default value' }) readonly propB!: string
    @Prop([String, Boolean]) readonly propC: string | boolean | undefined
}


// 上面的内容将會被解析成如下格式

export default {
    props: {
        propA: {
            type: Number
        },
        propB: {
            default: 'default value'
        },
        propC: {
            type: [String, Boolean]
        }
    }
}
           

使用 Vuex

關于怎麼使用Vuex此處就不再做過多說明了,需要注意的一點是,如果你需要通路

$store

屬性的話,那麼你必須得繼承

Vue

類,坑的地方是在某些情況下即使你沒有繼承

Vue

,它也能通過編譯,隻有在程式運作起來的時候才報錯。

class ExampleApi extends Vue {

    public async getExampleData() {
        if (!this.$store.state.exampleData) {
            const res = await http.get('url/exampleData');
            if (res.result) {
                this.$store.commit('setExampleData', res.data);
                return res.data;
            } else {
            promptUtil.showMessage('get exampleData failed', 'warning');
            }
        } else {
            return this.$store.state.exampleData;
        }
    }
}
           

使用自己的配置(含代理)

vue.config.js

是一個可選的配置檔案,如果項目的根目錄中存在這個檔案,那麼它會被

@vue/cli-service

自動加載,它的配置項說明可以檢視配置參考。

我們再開發過程中都會使用代理來轉發請求,代理的配置也是在這個檔案中,它的官方說明在devserver-proxy中,下面是一個簡單的

vue.config.js

檔案例子。

module.exports = {
    filenameHashing: true,
    outputDir: 'dist',
    assetsDir: 'asserts',
    indexPath: 'index.html',
    productionSourceMap: false,
    transpileDependencies: [
        'vue-echarts',
        'resize-detector'
    ],
    devServer: {
        hotOnly: true,
        https: false,
        proxy: {
            "/statistics": {
                target: "http://10.7.213.186:3889",
                secure: false,
                pathRewrite: {
                    "^/statistics": "",
                },
                changeOrigin: true
            },
            "/mail": {
                target: "http://10.7.213.186:8888",
                secure: false,
                changeOrigin: true
            }
        }
    }
}
           

讓 Vue 識别全局方法和變量

我們在項目中都會使用一些第三方 UI 元件,比如我自己就使用了 Element,但是在使用它的

$message

$notify

等方法時就直接報錯了,究其原因就是

$message

等屬性并沒有在 Vue 執行個體中聲明。

官方對此給出了很明确的解決方案,使用的是 TypeScript 的 子產品補充特性,可以檢視增強類型以配合插件使用。既然知道是因為沒有聲明導緻的錯誤,那我們就給它聲明一下好了,在

src/shims-vue.d.ts

檔案中添加如下代碼即可,如果沒有該檔案請自行建立。

看到網上也有一部分人說的是

src/vue-shim.d.ts

,反正不管是怎麼命名這個檔案的,它們的作用是一樣的。
declare module 'vue/types/vue' {
    interface Vue {
        $message: any,
        $confirm: any,
        $prompt: any,
        $notify: any
    }
}
           

這裡順道提一下,

src/shims-vue.d.ts

檔案中的如下代碼是為了讓你的 IDE 明白以

.vue

結尾的檔案是什麼玩意兒。

declare module '*.vue' {
    import Vue from 'vue';
    export default Vue;
}

           

路由懶加載

Vue Router 官方有關于路由懶加載的說明,但不知道為什麼官方給的這個說明在我的項目裡面都沒有生效,但使用

require.ensure()

按需加載元件可以生效。

// base-view 是子產品名,寫了相同的子產品名則代碼會被組織到同一個檔案中
const Home = (r: any) => require.ensure([], () => r(require('@/views/home.vue')), layzImportError, 'base-view');

// 路由加載錯誤時的提示函數
function layzImportError() {
    alert('路由懶加載錯誤');
}
           

上面的方式會在編譯的時候把檔案自動分成多個小檔案,編譯後的檔案會以你自己命名的子產品名來命名,如果代碼之間有互相依賴,依賴部分代碼編譯後的檔案會以兩個子產品名相連後進行命名。

但是需要注意的是,這樣拆分小檔案之後引入了另外一個新的問題,因為用戶端會緩存這些編譯後的 js 檔案,如果功能 A 同時依賴了

a.js

b.js

兩個檔案,但使用者在使用其它功能時已經把

a.js

緩存到本地了,使用功能 A 時需要請求

b.js

檔案,這時程式就很容易報錯,因為此時在用戶端這兩個檔案不是同一個版本,是以可能導緻

a.js

調用

b.js

中的方法已經被删了,進而導緻用戶端頁面異常。

關于引入第三方包

項目在引入第三方包的時候經常會報出各種奇奇怪怪的錯誤,這裡僅提供我目前找到的一些解決辦法。

/*
 引入 jquery 等庫可以嘗試下面這種方式
 隻需要把相應的 js 檔案放到指定檔案夾即可
**/
const $ = require('@/common/js/jquery.min.js');
const md5 = require('@/common/js/md5.js');
           

引入一些第三方樣式檔案、UI 元件等,如果引入不成功可以嘗試建一個 js 檔案,将導入語句都寫在 js 檔案中,然後再在

main.ts

檔案中導入這個 js 檔案,這個方法能解決大部分的問題。例如我先建了一個

lib.js

,然後在

main.ts

中引入

lib.js

就沒有報錯。

// src/plugins/lib.js
import Vue from 'vue';

// 樹形元件
import 'vue-tree-halower/dist/halower-tree.min.css';
import {VTree} from 'vue-tree-halower';
// 餓了麼元件
import Element from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
// font-awesome 圖示
import '../../node_modules/font-awesome/css/font-awesome.css';
import VueCookies from 'vue-cookies';
import VueJWT from 'vuejs-jwt';

Vue.use(VueJWT);
Vue.use(VueCookies);
Vue.use(VTree);
Vue.use(Element);


// src/main.ts
import App from '@/app.vue';
import Vue from 'vue';
import router from './router';
import store from './store';
import './registerServiceWorker';
import './plugins/lib';

Vue.config.productionTip = false;

new Vue({
    router,
    store,
    render: (h) => h(App),
}).$mount('#app');
           

因為第三方包寫的各有特點,在引入不成功的時候基本也隻能是見招拆招,當然如果你的功底比較深厚,你也可以自己寫一個

index.d.ts

檔案,實在不行的話,那個特殊的元件不使用 TypeScript 來寫也能解決,我目前還沒有找一個可以完全解決第三方包引入錯誤的方法,如果您已經有相關的方法了,希望能與你一起探讨交流。