接到《京保养》项目需求,了解到是移动端项目,运用于微信公众号及京东
APP
。通过与后端研发沟通,后端将提供所有的数据展示接口,这样最终商定使用前后端分离技术,而作为前端这边就非常适合选择基于 webpack + Vue 的单页面应用来实现。
前期组内也有基于单页面应用的项目总结,他们的总结的确让我在本项目中少走了很多弯路,但是不同的项目又遇到了不同的新问题,本文将会介绍我所遇到的新问题及解决方案。
感兴趣的同学可以通过以下两个入口先去体验下京保养应用,然后回来接着看文章:
-
微信公众号搜索“京东汽车用品” – 关注公众号 – 菜单栏“京保养”,见图1;
-
京东 APP – 我的 – 我的爱车 – 京保养,见图2。
图1
图2
如果你在 APP 中找不到“我的爱车”入口,你得先在京东 APP – 我的 – 设置 – 添加档案 – 我的爱车 – 绑定自己的爱车,然后才会有入口。
为什么要使用 Vuex
初拟技术选型,项目开始了,而开发过程中发现,项目中有不同的表单视图需要大量数据的共享。而仅使用单页面的路由来传参并不能满足需求,因为数据量过大,导致路由传参过于复杂。如此,项目中引进 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 就可以改变它的默认查找行为,问题也就解决了。
数据请求方案及 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
=>
{
//…
});
|