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