0%

2019-11-19 vue-cli+typescript 开发 web 应用心得与 bug

2019-11-19 vue-cli+typescript 开发 web 应用心得与 bug

一、vue-cli 新建项目及打包优化

vue-cli 官网:https://cli.vuejs.org/zh/

在本次的项目中,由于使用了 vue-cli 脚手架,在搭建项目的过程中可以说是方便了不少,没有了配置 webpack 的痛苦。

不过,在此之中也有些坑要注意就是了。

例如风格问题。vue-cli 默认的风格是 2 空格缩进、有分号风格,而本人习惯于 4 空格缩进、无分号风格,因此需要修改 .eslint.js 文件来重新约束风格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// .eslint.js 
module.exports = {
rules: {
indent: [// 强制使用一致的缩进
'error',
4, {//4格缩进
SwitchCase: 1, // case 子句将相对于 switch 语句缩进 4 个空格,即一个tab
},
],
'linebreak-style': [// 强制使用一致的换行风格
'error',
'unix'//使用\n而不是\r\n换行,这是因为大多数项目都是部署在Linux服务器上的,可以避免一些问题
],
quotes: [// 强制使用一致的反勾号、双引号或单引号double
'error',
'single',//使用单引号
],
semi: [// 要求或禁止使用分号代替 ASI
'error',
'never',//禁止不必要的分号
],
}
}

第二个问题就是 vue.config.js 的配置,开发阶段本人希望启动时能够自动打开浏览器,而且希望自定义端口。因此要在项目根目录的 vue.config.js 中添加如下代码

1
2
3
4
5
6
7
8
9
//vue.config.js
module.exports = {
devServer: {
open: true, //自动打开浏览器
port: 3000, //设置端口
hot: true, //启用热更新
compress: true, //是否启用gzip压缩
},
}

之后在 build 生成生产环境用的 dist 文件时,发现还额外生成了很多 map 文件,生产环境用不到,因此去掉。

1
2
3
4
//vue.config.js
module.exports = {
productionSourceMap: false,//移除生产环境的 source map
}

打包后发现 js 和 css 文件有些大,因此希望通过 cdn 来加载依赖,减少打包后的体积。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//vue.config.js
module.exports = {
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
// 只在生产环境用cdn
return {
externals: {//左边是要排除的包,右边是对应的变量
vuetify: 'Vuetify',
vue: 'Vue',
vuex: 'Vuex',
axios: 'axios',
'vue-router': 'VueRouter',
'leancloud-storage': 'AV',
moment: 'moment',
'vue-clipboard2': 'VueClipboard'
}
}
}
},
chainWebpack: config => {
//// ~~使用 jsdelivr 提供的免费cdn,最好限制版本以免出现兼容性问题~~
// 提醒:由于 jsdelivr 在中国大陆地区停止服务,请切换为 unpkg、cdnjs、staticfile 等 CDN 服务商!!
const css = [
'https://cdn.jsdelivr.net/npm/animate.css@3.7.2/animate.min.css',
'https://cdn.jsdelivr.net/npm/@mdi/font@4.5.95/css/materialdesignicons.min.css',
'https://cdn.jsdelivr.net/npm/material-design-icons-iconfont@5.0.1/dist/material-design-icons.min.css',
'https://cdn.jsdelivr.net/npm/vuetify@2.1.10/dist/vuetify.min.css'
]
const js = [
'https://cdn.jsdelivr.net/npm/axios@0.19.0/dist/axios.min.js',
'https://cdn.jsdelivr.net/npm/leancloud-storage@3.15.0/dist/av-min.js',
'https://cdn.jsdelivr.net/npm/moment@2.24.0/moment.min.js',
'https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js',
'https://cdn.jsdelivr.net/npm/vuetify@2.1.10/dist/vuetify.min.js',
'https://cdn.jsdelivr.net/npm/vue-router@3.1.3/dist/vue-router.min.js',
'https://cdn.jsdelivr.net/npm/vuex@3.1.2/dist/vuex.min.js',
'https://cdn.jsdelivr.net/npm/vue-clipboard2@0.3.1/dist/vue-clipboard.min.js',
'https://cdn.jsdelivr.net/npm/string-polyfills@0.9.1/distrib/String.min.js',//解决String(...).padStart的兼容性

]
let cdn = {}
if (process.env.NODE_ENV === 'production') {
config.module
.rule("images")
.test(/\.(jpg|png|jpeg|gif|bmp)$/)
.use("url-loader")
.loader("url-loader")
.options({
limit: 10240,
publicPath: 'http://cdn.cmyr.ltd/img/',//由于https下无法加载http源的js和css,因此只将图片上传到cdn
outputPath: 'img',
name: '[name].[hash:8].[ext]',
}).end();

cdn = {
css,
js
}
config.plugin('html').tap(([options]) => {
options.cdn = cdn
return [options]
})
} else {
cdn = {
css,
js: []
}
config.plugin('html').tap(([options]) => {
options.cdn = cdn
return [options]
})
}
},
}

1
2
3
4
5
6
7
8
9
10
11
12
<!-- index.html中的head部分添加如下代码 -->
<head>
<!-- 使用CDN的CSS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
<% } %>
<!-- 使用CDN的JS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn &&
htmlWebpackPlugin.options.cdn.js) { %>
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
</head>

二、Vuetify1.5 升 2.x 的坑

官网:https://vuetifyjs.com/zh-Hans/

这个坑的点在于 Vuetify 出现了版本不兼容的更新。

super plugin 这个项目刚开始写的时候 Vuetify 还是 1.5.x,后来重制时才发现出现了版本升级,理所应当的是出现了不兼容的更新。

不兼容的问题如下:

  1. Text alignment 问题

    1. 使用 text-sm-center 貌似无法正常居中,但使用 text-center 就可以。考虑到本项目的文字排版在手机端与电脑端一致,因此就用 text-center
  2. v-btn 问题

    1. 扁平样式属性由 flat 改为 text

    2. block 属性改为了

      1
      min-width: 100% !important;

      结果导致在 v-card-actions 中两个按钮无法并排放(也就是各占一半的行)

      无奈之下只能自己写样式覆盖:

      1
      min-width: auto !important;
  3. v-card v-card-text

    1. 这个的问题是字体颜色不对,默认变为了灰色,因此自己写样式覆盖
  4. v-card-title 问题

    1. 不知道为什么导致了 h1 添加样式后不会变化,因此改成 p 标签或 span 标签

      1
      2
      3
      <v-card-title>
      <h1 class="display-1">hello world</h1>
      </v-card-title>
  5. v-data-table

    1. v-data-table 相比之前也有很大变化,之前的写法也是完全不行了,这里给出一个参考写法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      <template>
      <div>
      <v-data-table
      :headers="headers"
      :items="list"
      :items-per-page="10"
      :sort-desc="true"
      :footer-props="footerProps"
      class="elevation-1"
      >
      <template v-slot:item="{ item }">
      <!-- 自定义item模板 -->
      <tr>
      <td>{{ item.id}}</td>
      <td class="text-center">{{ item.msg }}</td>
      <td class="text-center">{{ item.time }}</td>
      </tr>
      </template>
      <template v-slot:no-data>
      <v-alert :value="true" color="error" icon="warning">抱歉,当前还没有任何数据</v-alert>
      </template>
      </v-data-table>
      </div>
      </template>

      <script lang="ts">
      import Vue from 'vue'
      export default Vue.extend({
      name: 'Test',
      data: () => ({
      headers: [
      {
      text: 'id',
      align: 'left',
      },
      {
      text: '消息',
      align: 'center',
      },
      {
      text: '时间',
      value: 'time',
      align: 'center',
      sort: (a: Date|string, b: Date|string) => {
      return new Date(a).getTime() - new Date(b).getTime()
      }
      }
      ],
      footerProps: {
      showFirstLastPage: true,
      itemsPerPageText: '每页条数',
      itemsPerPageOptions: [10, 20, 50, 100, { text: '全部', value: -1 }]
      },
      list:[]
      }),
      props: {},
      // 需要标注有 `this` 参与运算的返回值类型
      methods: {},
      computed: {},
      components: {},
      watch: {},
      created() {},
      })
      </script>

      <style scoped>
      </style>

暂时先想到了这么多坑,之后如果又遇到了别的会继续写。

待解决:

  1. 如何压缩 index.html 页面中的 css 与 js 代码,之前用 webpack 手动配置时是可以用 uglifyjs-webpack-plugin 来压缩的,但 vue-cli 中不能。也没从谷歌和百度搜到答案,因此暂时搁置

三、leancloud 的使用

本次项目使用的数据库是云数据库 leancloud,官网https://leancloud.cn/

leancloud 有着非常完成的 SDK 来支持开发,免去了搭建服务器后台,可以让开发者专注在前端实现业务逻辑

本项目新应用的地方是 leancloud 提供的云函数和 hook 功能(不过云函数其实还是要写后端的内容,不过这个代码是扔给 leancloud 来运行,对于开发者而言还是省事了很多,也免去了复杂的接口验证)

云函数是在客户端调用服务器定义好的函数,对于一些需要控制权限的逻辑十分方便,也能更好的保护用户的数据。

而 hook 则是保证了对数据库的操作永远会有日志记录,因此可以排查恶意请求

使用例见官网。

【顺便,在使用的过程中,我还向 leancloud 提了一个意见,反馈了两个 bug,hhhh】

四、TypeScript 相关

一、TypeScript 的优点

本项目和之前的版本最大的区别就是开发语言从 JavaScript 转到了 TypeScript。

不得不承认,在开发项目时,有 TypeScript 提供的强大的类型校验功能,真的很方便,可以说规避了很多潜在错误。

虽然在一定程度上麻烦了,失去了 js 的自由,但为了强大的类型提示功能,我觉得是完全值得的。而且我也激进的认为,npm 上所有发布的包,都应该有类型提示功能,否则就是一个残废的包。

为什么这样说呢?如果用 ts 来开发,完成后是有完整的类型提示的,即便懒癌发作不想写文档,但只要注释足够依旧是可以的。而 js 开发的时候,是没有完整的类型提示的,没有文档的情况下使用起来实在是太过艰难

二、使用 async/await 改造异步函数

第二个进步是深入了解了 async/await 语法糖的用法。

学习过 js 的人都知道回调地狱是什么一个情况,也就是异步函数中回调一层套一层。

而我估计也有人学过利用 Promise 对象来改造异步函数,采用 Promise 链的方式来解决异步函数问题。

而在 ES7 当中,async/await+Promise 成为了解决异步函数的终极方案,它可以让开发者像写同步函数那样写出异步函数!

一个简单的异步函数例子如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 延时一段时间
* @param {number} time
* @returns
*/
async function sleep(time: number) {
return new Promise(resolve => setTimeout(resolve, time))
}
//上面的写法过于简洁,这其实相当于这种写法
async function sleep(time) {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, time)
})
}
//调用时
(async () => {//看出来了吗?这是一个匿名异步箭头函数,相当简洁~
console.log(Date.now())
await sleep(1000)//这样就会在1秒后再执行下面的函数
console.log(Date.now())
})()

当然了,由于 await 只能在 async 函数中使用,因此调用到 async 函数的地方,也必须是 async 函数。

上面的例子中异步函数只有执行成功的 resolve,下面来看看如果加上执行失败会是什么情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async function asyncFun() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {//50%概率成功和失败
resolve('异步函数执行成功!')
} else {
reject('异步函数执行失败!')
}

}, 500)
})
}

(async () => {
try {
let res = await asyncFun()
console.log('res', res)//打印成功结果
} catch (error) {
console.log('error', error)//打印失败结果
}
})()
1
2
3
4
//控制台输出结果
res 异步函数执行成功!
res 异步函数执行成功!
error 异步函数执行失败!

看见了没?如果是调用了 resolve 返回的结果,是异步函数执行成功的结果,因此会在 try-catch 中的上部分代码块,而如果调用了 reject,则是会到下半部分代码块。一般来讲也会用 reject 抛出一个异常来,需要在这个地方进行异常处理。

在这里需要重点指出,当一个 try-catch 块中存在多个 await 时,一旦有一个 await 抛出了异常,后续的 await 都不会继续执行,而是转到异常处理上去。

因此,理论上来讲,每一个用到了 await 的地方都需要 try-catch 包裹才对。

不过,当异常已经在底层的 async 函数中捕获时,上层的 async 函数就接受不到这个异常了,因此可以不加 try-catch 处理而是对返回值进行判断。

五、vue 项目中复制文本到剪切板

在本项目中有一个需求就是复制链接到剪切板,找了一圈后发现还是用现成的包吧。

代码如下

1
2
3
4
//main.ts
import VueClipboard from 'vue-clipboard2'
VueClipboard.config.autoSetContainer = true // add this line
Vue.use(VueClipboard)
1
2
3
4
5
6
let url = 'https://www.baidu.com'
try {
this.$copyText(url)//直接调用这个api即可,想复制啥都行
} catch (error) {
console.error(error)
}

六、关于 vscode 中遇到的问题

问题 1:使用 RamMap 查看时大量出现 git.exe、rg.exe、svhost.exe、conhost.exe 等进程,导致内存爆炸

初步判断是 git 的原因,不管是百度还是谷歌都暂时没有找到合适的解决方案,只能关闭 vscode 中自带的 git,但仍有进程在莫名其妙的不断创建。

之前在 vscode 中安装过一个插件 Resource Monitor ,使用 Process Explorer 查看后注意到这货在一直创建进程。但是即便卸载插件之后,git 的问题也依旧存在。

目前没有找到解决方案,只能先暂时搁置

本文作者:草梅友仁
本文地址: https://blog.cmyr.ltd/archives/bdb53d47.html
版权声明:转载请注明出处!

坚持原创技术分享,您的支持将鼓励我继续创作!