记录一下对最近正在做的Vuejs框架的项目的思考、实践过程中踩的坑和学习到的东西。
背景
上周起,公司开启了一个新的项目。
将早先使用传统的基于jQuery的多页面架构的一个演示系统利用Vuejs框架以SPA的架构来进行重构(重写?)。
整个系统利用jQuery实现时大概有近30个页面,不过页面主要以数据展现为主,只有少部分页面才有着交互功能。
因为公司目前从事前端工作的就我一个人,所以这个项目暂定由我一个人来完成前端方面的工作。
后端API方面,因为系统使用的是第三方提供的数据API,所以只是由公司的PHP
和JAVA
后端同事对三方API进行封装和简单加工,比如排序、过滤、适配数据格式和计算出前端能直接展现的数据,方便我这边调用。
项目上面,别人先提供了一个早先完成了一小部分的项目模板,可以复用一部分通用组件和项目架构,不过同时也限制了一些项目依赖的版本。
毕竟我并没有过使用Vue进行工作中的项目开发的经验,提供的模板也在一定程度上给我提供了一个项目架构,否则的话我只能去Github上面Down一个来参考着开发了。
不过在实际使用中,我也根据自己的需求和社区推荐的最佳实践来对早先的项目模板进行了重构,来适应自己的开发习惯。
工期暂定为65天左右(不包括周末),目前已经过去了一周。
我个人对本次Vuejs框架的项目是十分期待的,毕竟先前的工作没有做过这方面的,想积累一些使用框架和UI库来进行开发的经验。
之前的工作样式方面都以手写样式为主。
毕竟只有简单利用开发过个人小项目的经验----先前使用Vuejs框架简单实现过知乎日报,可想而知本次开发过程中会遇到很多坑点,所以我决定将本次开发经历详细地记录到此篇博文里。
顺便最近利用下班时间一直在看一本书<<Vue.js前端开发快速入门与专业应用>>,希望能对开发过程有所帮助。
技术栈
关于技术栈这方面,并没有什么规定,Vuejs全家桶是少不了的。
项目使用Vue-cli
的Webpack模板进行创建,所有项目打包工具也自然而然地使用了Webpack。
因为有先前实现过几个页面的Demo,封装的一部分组件是基于Element-UI的,而且Element-UI也是Vuejs实现后端系统大家都在使用的UI库,所以UI库仍然使用Element-UI。
代码风格检查使用了ESLint,它也是Vuejs
官方推荐的代码风格检查工具,而且它是完全可配置的。代码风格的规定继承了Standard规范(Vue-cli创建项目时可选),然后再根据自身代码风格对部分规则进行了覆盖和重写。
数据请求方面,早先的Demo使用了whatwg-fetch模块来直接封装Common-Query API
,因为我使用的是后端二次封装后的API,所以将数据请求使用的模块改为了Vuejs官方推荐的Axios并且引入了qs模块来发送POST
请求。
动画方面虽然页面以数据展示为主,设计的动画也并不多,不过为了增强用户体验,仍然引入了动画库Animate.css。
时间格式化方面使用了Moment模块。
接口返回的数据处理方面,根据需求来按需引入lodash模块。
CSS预处理器方面,因为提供的组件使用了Sass,所以我这边也仍然使用它。
以上各种模块的对应版本如下表:
模块名 | 版本号 |
---|---|
vue | v2.5.2 |
vue-router | v2.6.0 |
vuex | v2.3.1 |
axios | v0.18.0 |
element-ui | v1.4.13 |
animate.css | v3.6.1 |
lodash | v4.17.10 |
moment | v2.22.1 |
qs | v6.5.2 |
项目创建
项目使用Yarn来进行依赖包的下载和管理,使用Vue-cli进行项目创建。
使用Vue-cli创建项目,使用vue-router
,使用eslint
,不使用单元测试和端对端测试。
打开./config/index.js
,修改autoOpenBrowser
为true
,修改notifyOnErrors
为false
(这个功能太烦人了。)
运行项目
1 | $ yarn dev |
项目目录
虽然有先前实现过部分页面的Demo
,不过整个项目仍然是我从零开始创建的,毕竟自己走一遍流程,会更加熟悉项目的结构。
项目文件目录如下:
1 | project |
配置ESLint
因为提供的Demo和组件的代码风格和我有很大的不同,而接下来的开发周期很可能只会是我一个人参与,所以我根据自己的代码风格重新配置了ESLint。
忽略对提供文件的代码检查
在./eslintignore
文件中添加src/components/*
忽略对所有提供组件的代码风格检查。
根据需要配置新的检查规则
新的代码风格检查规则如下:
1 | // https://eslint.org/docs/user-guide/configuring |
配置Babel
为了优化打包后体积,我们采取按需加载Element-UI
的方式(需要使用babel-plugin-component
插件),所以需要在.babelrc
文件中进行相关的配置。
最终配置文件如下所示:
1 | { |
引入Babel-polifill
Vuejs中的.babelrc
中引用了babel-plugin-transform-runtime
这个插件来帮助转译ES6方法。虽然这个插件已经足以在大多情况下,满足我们转译ES版本的需求。但同时,它仍然存在着2个问题:
- 异步加载组件的时候,会产生polyfill的代码冗余
- 不支持对全局函数和实例方法的polyfill
它无法转译例如Promise,Set,Map这样的ES6新引入的全局函数,尤其是不支持转译Promise,对代码兼容性有很大的影响。
同时,它不支持大多数组、字符串、对象等ES6新添加的实例方法。
替换步骤如下:
- 卸载依赖
babel-plugin-transform-runtime
- 修改
.babelrc
文件,删除transform-runtime
- 在入口文件最前面(
main.js
)用代码import 'babel-polyfill'来引入
踩坑记录
无法修改UI库组件样式
.vue文件中的style
标签如果被设置了scoped
属性,那么最终会在编译生成的css上加上一个哈希值,那么这个新的选择器就无法选择到Ui库的样式。
所以要覆盖UI库的样式,就不能给组件加scoped
属性。这时候我们可以使用BEM命名法来防止样式冲突。
给元素绑定行内样式
使用v-bind:style
如下形式来绑定行内样式:
1 | <foo-bar :style="{ width: value }" /> |
如果有多条样式,建议以对象的方式传入
异步更新组件内容无法触发视图更新
原因,组件在异步更新之前已经渲染,这时候父子组件间还没有通过props
来进行传值。然后在created mounted
的生命周期函数内,会使用默认值来进行渲染。
解决方法:
- 在需要异步更新的组件上增加
v-if
属性,通过在异步操作结束后更新标志位的方式来触发组件更新。 - 使用watch功能,监听
props
传入的数据,手动更新,覆盖当前data返回的数据,触发视图更新。
路由访问
组件内访问当前路由使用this.$route
。
组件内调用路由的方法使用this.$router
。
渲染样式完全不同的列表
在v-for
内部使用template
内置组件,在上面使用v-if
,v-else
等逻辑,来进行条件渲染。
当组件挂载到DOM上时候,template
标签会自动移除。
1 | <ul class="list"> |
UI库组件绑定的事件无法触发。
原因: Vue2.0开始,为自定义组件绑定原生事件必须使用.native
修饰符。
1 | <my-component @click.native="handleClick">Click Me</my-component> |
打包后样式丢失
开发时候使用了-webkit-box-orient: vertical;
来控制文本多行溢出隐藏。
但是经过webpack
打包后部署到线上发现该条样式丢失了,查询资料后发现是被autoprefixer
移除了。
解决方式1:在该条样式前后分别加上/*! autoprefixer: off */
和/*! autoprefixer: on */
来跳过对该条样式的处理,但在开发过程中,却会报一堆警告。
解决方式2: 编辑package.json
文件内的browserlist字段,在其中添加Safari 6,打包后即可正常。
Axios的POST请求无法发送数据。
因为axios
会对post的参数进行处理,所以根据查询到的资料,使用了qs
库。
但是今天重新装了一遍依赖后,axios的请求不再发送数据,反而注释掉了有关qs库的使用后正常了。
可能原因:先前我的yarn.lock
文件保存到所有包的仓库都是yarn
的官方仓库下载的,前两天我将仓库默认地址切换到了淘宝的地址,所以应该lock
文件也无法保证下载的依赖是完全相同的了。
使用Element-UI封装的地域联动选择插件无法绑定初始值。
项目中,有个配置页面,支持2种操作。
- 创建一条配置,默认地域选择为全部。
- 更新某条配置,需要绑定先前设置的地域配置。
在开发种选择了使用Element-UI的Select组件进行封装,来实现地域选择的二级联动效果。
使用HTTP请求,读取地域数据(Json)。再迭代数据,获取到所有的省份,绑定给省级区域下拉列表,再根据省级区域的选择结果读取对应的市级地域数据,渲染出市级区域下拉列表供用户选择。
但在更新值的时候,发现一次请求得到的地域信息值,无法同时将省级区域和市级区域绑定到二级地域联动选择上。
思考:个人觉得可行的办法是,在初次未进行选择的时候,市级区域,默认可供选择的区域数据为所有的市级区域。(数据量过大)
最终商量后,解决方案修改为使用Element-UI的Cascade插件来实现二级地域联动效果。这个组件本身便是支持级联选择的,同时可以给组件绑定默认值。
前端返回需要图片,后端数据返回无图片。
首次在Vue项目中遇到这样的问题。
从设计的角度来说,列表内的新闻项都应该有新闻相关图片,但是三方API有时候并不能提供图片,新闻项的图片字段为null。
那么,首先想到的解决方案就是,在图片加载失败的时候,选择展现默认图片,并将其地址赋值给图片的src属性,再将这个错误事件清除掉,避免多次触发。实现如下:
1 | <img :src="images.jpg" onerror="this.src='path_to_the_pic/pic.jpg'; this.onerror=null;" /> |
但在实际应用中,发现如何设置图片地址是个问题。
因为vue项目在经过webpack
打包后,会将相关静态文件打包到static
目录下,并且给文件加上哈希值。
经过查询资料后,知道了应该让webpack处理这个图片,那么打包后的引用关系就能正常。
实现方法就是:onerror="this.src=' " + require('@/assets/default.png') + "'"
,使用require
方法引入图片。
但是默认图片仍然无法正常展示。
开始,我怀疑是因为单双引号的问题,导致内联js代码
无法正常执行。
后来,经过多次替换括号类型等,发现并不能实际解决问题。
仔细研究良久后恍然大悟,因为使用v-bind
给图片绑定src
属性,但是绑定的内容为null
,所以图片根本不会发起资源请求,那么也便不存在失败一说,继而无法触发onerror
事件。
解决:在项目的Store文件夹下的modules
中,寻找对应的JS模块。
将图片通过webpack
处理,获取处理后的图片引用地址。
使用map
方法,改变actions
里,判断新闻项的图片字段是否为空,若为空将其修改为默认的图片链接。
vm.$nextTick使用
将回调延迟到下次DOM更新循环完成之后执行,在修改数据后立即使用它,然后等待DOM更新。
与全局的Vue.nextTick不同的是,它的回调函数的this自动绑定为调用它的组件实例。
应用场景
- 在Vue的
created
生命周期中进行DOM操作,一定要放到vm.$nextTick
的回调中。因为此时DOM
可能尚未渲染。而mounted
生命周期函数中DOM已经挂载,就不存在此问题。 - 在数据变化后要执行某个操作,而这个操作需要使用随数据改变而改变的DOM时,这个操作需要放入
vm.$nextTick
的回调函数中。
1 | const _this = this; |
路由按需加载
将路由按需加载可以减少首屏体积,提升首屏加载效率,增强用户体验。
1 | // 普通的加载方式 |
多行文本框内容间插入符号且维持光标位置
实现方式,见下面的DEMO。