专栏名称: 京东设计中心JDC
专业,创造力,激情,设计。京东用户体验设计部门,致力于创造更美好的电子商务购物体验。
目录
相关文章推荐
班主任家园  ·  你的位置(值得一看!) ·  昨天  
重庆日报  ·  速看!重庆这些学校开学时间来了→ ·  昨天  
株洲晚报  ·  寒假余额已不足!开学时间表来了→ ·  2 天前  
株洲晚报  ·  寒假余额已不足!开学时间表来了→ ·  2 天前  
一叶目开  ·  新时代的育儿方向——有点学历的韦小宝 ·  5 天前  
一叶目开  ·  新时代的育儿方向——有点学历的韦小宝 ·  5 天前  
51好读  ›  专栏  ›  京东设计中心JDC

JDC丨京东设计中心 - 《京保养》基于Vue+Vuex的单页面应用实践

京东设计中心JDC  · 公众号  ·  · 2017-11-23 17:18

正文

接到《京保养》项目需求,了解到是移动端项目,运用于微信公众号及京东 APP 。通过与后端研发沟通,后端将提供所有的数据展示接口,这样最终商定使用前后端分离技术,而作为前端这边就非常适合选择基于 webpack + Vue 的单页面应用来实现。

前期组内也有基于单页面应用的项目总结,他们的总结的确让我在本项目中少走了很多弯路,但是不同的项目又遇到了不同的新问题,本文将会介绍我所遇到的新问题及解决方案。

感兴趣的同学可以通过以下两个入口先去体验下京保养应用,然后回来接着看文章:

  1. 微信公众号搜索“京东汽车用品” – 关注公众号 – 菜单栏“京保养”,见图1;

  2. 京东 APP – 我的 – 我的爱车 – 京保养,见图2。


图1

图2

如果你在 APP 中找不到“我的爱车”入口,你得先在京东 APP – 我的 – 设置 – 添加档案 – 我的爱车 – 绑定自己的爱车,然后才会有入口。

为什么要使用 Vuex

初拟技术选型,项目开始了,而开发过程中发现,项目中有不同的表单视图需要大量数据的共享。而仅使用单页面的路由来传参并不能满足需求,因为数据量过大,导致路由传参过于复杂。如此,项目中引进 Vuex 技术来实现数据共享。

拿项目中需要数据共享的地方举例 —— 绑定车辆模块。先来看下该模块的操作流程:

  1. 绑定车辆页面填写车牌号码;

  2. 填写车系,包含选择品牌、选择车系、选择年款;

  3. 回到了绑定车辆页面继续填写车辆绑定信息。

如果上字描述还不清楚,我也录了个小视频,点击查看交互流程:


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 就可以改变它的默认查找行为,问题也就解决了。

数据请求方案及 JSONP 请求

下面说说数据请求的方案:曾经我们常用的是 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 => {

//…

});







请到「今天看啥」查看全文