专业,创造力,激情,设计。京东用户体验设计部门,致力于创造更美好的电子商务购物体验。 |
接到《京保养》项目需求,了解到是移动端项目,运用于微信公众号及京东 APP 。通过与后端研发沟通,后端将提供所有的数据展示接口,这样最终商定使用前后端分离技术,而作为前端这边就非常适合选择基于 webpack + Vue 的单页面应用来实现。
前期组内也有基于单页面应用的项目总结,他们的总结的确让我在本项目中少走了很多弯路,但是不同的项目又遇到了不同的新问题,本文将会介绍我所遇到的新问题及解决方案。
感兴趣的同学可以通过以下两个入口先去体验下京保养应用,然后回来接着看文章:
微信公众号搜索“京东汽车用品” – 关注公众号 – 菜单栏“京保养”,见图1;
京东 APP – 我的 – 我的爱车 – 京保养,见图2。
图1
图2
如果你在 APP 中找不到“我的爱车”入口,你得先在京东 APP – 我的 – 设置 – 添加档案 – 我的爱车 – 绑定自己的爱车,然后才会有入口。
初拟技术选型,项目开始了,而开发过程中发现,项目中有不同的表单视图需要大量数据的共享。而仅使用单页面的路由来传参并不能满足需求,因为数据量过大,导致路由传参过于复杂。如此,项目中引进 Vuex 技术来实现数据共享。
拿项目中需要数据共享的地方举例 —— 绑定车辆模块。先来看下该模块的操作流程:
绑定车辆页面填写车牌号码;
填写车系,包含选择品牌、选择车系、选择年款;
回到了绑定车辆页面继续填写车辆绑定信息。
如果上字描述还不清楚,我也录了个小视频,点击查看交互流程:
Vuex 的使用
Vuex 的具体使用,有几个核心概念:
1.state —— 定义存储状态;
2.getter —— 对数据进行过滤;
3.mutation —— 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation;
4.action —— 类似于 mutation,不同在于可以包含任意异步操作;
5.modules —— 如果应用过大,便可以使用 modules 来分割管理,不至于 store 变得非常臃肿。
store 实例具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import Vuex from 'vuex' ; //引入 Vue . use ( Vuex ); //使用 export default new Vuex . Store ({ state : { formParams : {}, address : {} }, actions : { GET_SERIES_LIST : function ({ commit }, params ) { axios . get ( url , { params : data , withCredentials : true }) . then ( response => { commit ( 'setBrandsList' , { data }); }); }, }, mutations : { setAddress : ( state , data ) => { state . address = data ; } }, getters : {}, modules : {} }); |
用法举例:
子组件中读取 state 状态的方法示例:
1 2 3 |
computed : { address () { return store . state . address ; } } |
组件通过 commit 修改 state 状态的示例:
1 |
this .$ store . commit ( 'setAddress' , params ); |
组件通过 dispatch 触发 action 调用示例:
1 |
this .$ store . dispatch ( 'GET_SHOPS_LIST' , params ); |
Vuex 的持久化存储方案
以上 Vuex 用起来的确非常方便,解决了多视图之间的数据共享问题。但是运用过程中又带来了一个新的问题是,Vuex 的状态存储并不能持久化。也就是说当你存储在 Vuex 中的 store 里的数据,只要一刷新页面,数据就丢失了。
那本人很快就想到了要用 sessionStorage 或者 localStorage 的方式来解决此问题。解决方式便是在每个 mutations 中做一次 storage 的存值,因为 mutations 是改变 state 的唯一途径,所以每一次改变都进行 storage 的存值也不会有问题。
1 2 3 4 5 6 |
mutations : { setAddress : ( state , data ) => { state . address = data ; window . localStorage . setItem ( ' address ' , data ); }, //… |
那其实以上手动存取 localStorage 的方式还可以做得更简便。那就是引入 vuex-persist 插件,它就是为 Vuex 持久化存储而生的一个插件。不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者 localStorage 中。具体用法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 1 |
import VuexPersistence from 'vuex-persist' ; //引入 const vuexLocal = new VuexPersistence ({ //配置 storage : window . sessionStorage }); export default new Vuex . Store ({ state : { formParams : {}, address : {} }, actions : { }, mutations : { setAddress : ( state , data ) => { state . address = data ; } }, plugins : [ vuexLocal . plugin ] //添加插件 }); |
通过以上设置,在图3中各个页面之间跳转,如果刷新某个视图,数据并不会丢失,依然存在,并且不需要在每个 mutations 中手动存取 storage 。
当项目在本地开发还没有部署到测试环境时,项目的数据请求一律都是跨域,虽然很多文章都有说怎么解决跨域问题,但是在本项目中又遇到了个新问题。因为后端设置了指定的跨域允许域名(如:local.jd.com),并不是任何域名都可以开启 Chrome 的 CORS 后就可以跨域访问了(本方法详见文章“ 《京东E维》基于 vue+webpack 的单页面实践 ”)。
我们通常在本地开发时项目的访问路径为http://localhost:8080,指定的域名可跨域访问,只需要在本地配置 host: 127.0.0.1 local.jd.com,这样便可以以http://localhost:8080 的方式来访问了。但是如此配置后其中的数据接口还是提示跨域无法访问,后来发现 webpack-dev-server 的配置中是默认查找 hostname ,添加配置 disableHostCheck: true 就可以改变它的默认查找行为,问题也就解决了。
下面说说数据请求的方案:曾经我们常用的是 vue-resource ,但是 Vue 官方已经不建议使用了。Vue 的作者 Evan You 原话是这样说的:
最近团队讨论了一下,Ajax 本身跟 Vue 并没有什么需要特别整合的地方,使用 fetch polyfill 或是 axios、superagent 等等都可以起到同等的效果,vue-resource 提供的价值和其维护成本相比并不划算,所以决定在不久以后取消对 vue-resource 的官方推荐。已有的用户可以继续使用,但以后不再把 vue-resource 作为官方的 ajax 方案。
这里可以去掉 vue-resource,文档也不必翻译了。
链接: https://github.com/vuefe/vuejs.org/issues/186
因此项目中选择了 axios 来支持数据请求,但是 axios 并不支持 JSONP 请求,项目中有几处必需用到 JSONP 的接口,所以又引进了 JSONP 模块来支持 JSONP 的数据请求。调用示例:
1 2 3 4 |
jsonp ( '//d.jd.com/xxxx?fid=1&callback=getAreaListCallBack' ); window . getAreaListCallBack = function ( r ) { console . log ( r ); }; |
必要时需要做代码的封装
项目不断开发,发现 store 里的 action 堆积的代码越来越多,重复代码也不少。列举其中一个如下,其余都是类似:
1 2 3 4 5 6 7 8 9 10 11 12 |
actions : { //获取服务记录查询及记录列表 GET_SERVICE_LIST : function ({ commit }, params ) { axios . get ( this . state . host + '/xxxxxx' , { params : params , withCredentials : true }). then (( response ) => { commit ( 'setServiceList' , { list : response . data . data }); // params.success(response.data); }, ( err ) => { console . log ( err ) }); }, //… } |
可以看到 axios 的数据请求代码基本一致,而且要重复写无数遍,因此我将项目中的此种重复代码提出来放入单独的文件。数据请求类型可配置,参数可配置,接口访问路径可配置,这样减少了很多冗余代码,同时在修改某个通用配置的时候,只需要修改一处即可。具体封装如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1 |
xhr : function ( apiskey , params , changeState ) { let apis = this . apis ; let url = debug ? host + apis [ apiskey ]. url : apis [ apiskey ]. url ; let data = params . params || params || {}; let type = apis [ apiskey ]. type || 'get' ; if ( type == 'post' ) { axios . post ( url , data , { withCredentials : true }) . then ( response => { //… }, response => { //… }); } else { axios . get ( url , { params : data , withCredentials : true }) . then ( response => { //… }, response => { //… }); } } |
Vue 组件中第三方文件的引入
项目中有个别视图(图3)需要显示地图,而地图的引入需要引入第三方的地图库,如腾讯或者其他,当进入应用的时候,实际并不需要第三方的文件直接就加载进来,只在需要它的时候才加载就行。所以在显示地图的视图中,我做了如下处理,这样可以仅在该视图中才加载第三方地图 JS 库。
1 2 3 4 5 6 7 8 9 10 11 12 |
loadMap () { let self = this ; return new Promise ( function ( resolve , reject ) { window . initTheMap = function () { resolve ( self . initMap ()); } var script = document . createElement ( 'script' ) script . type = 'text/javascript' script . async = true script . src = '//map.qq.com/api/js?v=2.exp&callback=initTheMap&key=' + self . k script . onerror = reject document . head . appendChild ( script ); }); } |
图3
项目中所有的视图都可以视为组件,我这里的所指的需要提取的组件,是指那些复用率高的模块和功能。例如:顶部条、弹框、吐司提示、无限加载等功能提取为单独的组件。组件的提取可以让自己的开发效率更高,同时也让项目方便维护。所以项目结构中有单独的文件夹专门存放复用率高的组件(图4)。
图4
组件的具体调用方式,举例顶部条的调用:
在主视图组件中,需要引入需要使用的组件:
1 |
import Header from '../component/header.vue' ; |
然后注册组件:
1 2 3 |
components : { jHeader : Header } |
模板中使用:
1 |
< j - header : title = "title" > j - header > |
顶部条所指的具体内容,如图5红色框中的区域。
图5
类似顶部条这样的组件基本在每个视图中都会用到,但是他们的布局和样式基本完全相同,除了标题文字可能有所区别,所以单独作为组件提取出来是非常有必要的。
未加入 Vue 的懒加载,本项目整个应用的 JS 文件大小大概为200多 KB ,暂且能接受,如果项目内容再多一些,可能会有更大的 JS 文件,导致初次进入应用等待时间较长,所以复杂的项目可以考虑加入懒加载方案,按需加载 JS 。
以上是项目中提取出来的一些比较棘手的问题,项目开发完成后,还有一个感触最深的就是 Vue 的模式是数据驱动视图,而曾经的 jQuery 的模式却是以 DOM 元素为中心,先查找 DOM ,再给 DOM 绑定事件,通过 DOM 元素渲染数据,或者使用模板等。而 Vue 不再需要模板语言,本身就带有模板语言性质,开发过程中更多的关注数据怎么处理就行,对于自身而言,感觉 Vue 的开发更加效率和便捷了,不同的尝试总会让人有意外的收获,这使人感到很兴奋。
本文到这就结束了,如果以上有什么不合理的地方,欢迎大家指正!
|
吃货研究所 · 选总统算个啥?腐国人民因为一条巧克力崩溃了... 8 年前 |
|
中国企业家杂志 · 早报 | 史玉柱清空中民投股份并辞去所有职务;韩民众要求逮捕三星副会长,特检组推迟决定是否抓李在镕 8 年前 |
|
钱江晚报 · 太吓人!央视最新曝光:只需手机号,就能把你身份财产查个底朝天! 8 年前 |
|
跟大厨学做菜 · 面粉这样做,简单好看又好吃,我能吃一年! 7 年前 |
|
好地网上海 · 【7.11官方发布】连接徐行-菊园-马陆-南翔的大通道明日试通车 7 年前 |