在此前我使用的前端框架是 Angular,使用过 TypeScript 后你就会讨厌 JS 了,我学习 Vue 时的最新版本是 2.5,相信大部分同学都不会认为 Vue 那样又细又长的代码很美观吧,简单看了一些网络博客后,我毅然决然引入了 TypeScript 进行开发,本文仅整理记录我自己遇到的一些坑。

使用 Cli

脚手架是一个比较方便的工具,这里需要注意的是@vue/clivue-cli是不一样的,推荐使用npm i -g @vue/cli安装。

安装完成后,可以直接使用vue create your-app创建项目,你可以选择使用默认配置亦或是自己手动选择配置,按提示一步一步向下走即可,它会根据你的选择自己创建比如tsconfig.json等等配置文件。这里推荐使用less开发样式,sass老是在安装的过程中出问题。

当然你也可以使vue ui命令启动一个本地服务,它是一个 Vue 项目管理器,提供了一个可视化的页面供你管理自己的项目,它的样子如下图所示,还是比较清新的。

使用 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.jsb.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 来写也能解决,我目前还没有找一个可以完全解决第三方包引入错误的方法,如果您已经有相关的方法了,希望能与你一起探讨交流。