Vue 2.0已经正式发布好长时间了。想找一个Vue.js Vuex vue-route的学习项目来练手。做个电商App吧,上gitHub搜索了一下搜到一大堆,不过基本上都不是使用单文件组件开发的,更不用说基于Vue.js全家桶了。本项目不一样的地方在于使用vue-cli + webpack template人开发模式,还要求Web和移动端一体化,也就是响应式Web,而且不能Mock数据,额外需要有一个提供restful web api的后端应用。哈哈,就是搞全栈呀。
一、后端服务
采用的是Express + mLab + Mongoose的技术栈。http模块加上Express自带路由利用中间件思想可以很方便地基于Node开发提供restful web api的Web应用。考虑到方便在线演示部署,数据库选用了MongoDB的云服务mLab。当然,提交数据到数据库时一般要选用便捷操作的对象模型工具(ORM)。在node.js和MongoDB数据库环境下,Mongoose作为就是不二之选了。具体实现请看https://github.com/szriafan/vue-shop-api。另外,我将node.js代码免费托管到了https://heroku.com/(主机数据库都免费,爽呀)。现在直接在浏览器访问https://vue-shop-api.herokuapp.com/v1/products就可以返回商品列表所需要的数据了,当然更好的选择是使用Postman之类的HTTP请求调试工具。如果我们不了解后端和mLab,只要在ations.js手动将axios的baseURL改为https://vue-shop-api.herokuapp.com/v1就可以了。或者对数据API足够了解,也可以不写Node.js应用的。为了方便大家全面学习测试,建议大家最好访问本地的restful应用。注意,访问不同端口的本地服务或不同域名的远程服务都有跨域问题,使用CORS可以很好解决这个问题。app.all('/*', function(req, res, next) { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-type,Accept,X-Access-Token,X-Key'); if (req.method == 'OPTIONS') { res.status(200).end(); } else { next(); }});好了,接下来集中精力开发前端。
二、前端
1. 运行vue-cli + webpack template就得到了一个Vue.js Vuex vue-route全家桶脚手架项目。我们要完成两个部分:前台展示和后台管理。
2. 前台展示有两个功能:展示商品列表和详情的功能、添加商品到购物车的功能。商品列表和详情页面都能添加商品到购物车,商品列表页只能通过点击按钮一次添加一件商品,但详情页面可以通过+-按钮或文本框输入来改变商品数量(不能大于库存),再通过点击添加按钮一次性添加多件商品。购物车列表页也可以可以通过+-按钮或文本框输入来改变商品数量,与详情页面操作不一样的是购物车列表页改变商品数量时会同步保存数据,即使重新刷新页面或关闭浏览器,都会显示已保存的数据。而但详情页面如果改变数量后没有按添加按钮就不会添加商品到购物车。
3. 后台管理有的功能就是实现商品及品牌的CRUD操作。在新增修改商品时,会通过下拉框选择品牌,而下拉框用品牌数据来填充的。因此商品数据是依赖品牌数据的,因此后台管理时先应该添加商品数据。
4. 明确了上述需求后,先看到一下最终效果吧。
5. 我们来说一说具体实现。SPA一般都有路由,复杂Vue.js应用页面之间的导航都要用到vue-route。vue-rout使用JavaScript动态初始化配置全局对象,将组件(components)映射到路由(routes),然后告诉vue-router在哪里渲染它们。通过注入路由器,我们还可以在任何组件内通过this.$router访问路由器,也可以通过 this.$route访问当前路由。比如说当添加或修改商品成功后我们就是通过this.$router回到商品列表页的。模板中的组件让用户在具有路由功能的应用中(点击)导航,其中to表示目标路由的链接,这个值可以是一个字符串或者是描述目标位置的对象,对应路由配置对象的path属性值或name对象。比如说App模板中的组件to属性设置为{name: 'Home'},通过路由映射会导航到Home组件所在的页面。建议设置to的属性值为name对象,因为这个对象一般很少改动,而首页链接设置成'/',同样是因为这个值很少改动。另外首页链接还得添加exact属性来精确匹配链接,大家都知道,其它path值肯定包含/,不设置该属性会造成导航链接样式都添加激活链接类名。
6. 如下面的文件路径树所示,本项目使用了大量的嵌套单文件组件,下面的项目components和pages文件夹包含了大量的单文件组件。嵌套单文件组件提高了代码复用性,让分模块团队开发变得容易,这对于开发大型Vue.js应用尤为重要。不过Vue组件使用单向数据流向下进行组件数据通信,大量的会让代码难以维护,此时我们就应该考虑用Vuex来实现多个组件共享状态。
├─components
│ ├─cart
│ │ CartControl.vue
│ │ CartItem.vue
│ ├─common
│ │ Loading.vue
│ ├─manufacturer
│ │ ManufacturerForm.vue
│ └─product
│ AddToCartButton.vue
│ ProductDetails.vue
│ ProductForm.vue
│ ProductItem.vue
│ ProductList.vue
├─pages
│ │ Cart.vue
│ │ Details.vue
│ │ Home.vue
│ └─admin
│ │ Index.vue
│ ├─manufacturer
│ │ Edit.vue
│ │ Manufacturers.vue
│ │ New.vue
│ └─product
│ Edit.vue
│ New.vue
│ Products.vue
如下面的文件路径树所示,本项目Vuex代码集中store文件夹中。Vuex应用的核心store(仓库),包含应用中大部分的状态,如商品、品牌、购物车数组, 我们是在index.js中定义的; Action函数接受一个与store实例具有相同方法和属性的context对象,可以包含任意异步操作,如对后台数据访问商品和品牌数据的异步操作,我们是在action.js中定义的;Vuex中的Mutation非常类似于事件:每个Mutation都有一个字符串的事件类型 (type) 和一个回调函数(handler)。如对异步回调函数和添加商品到购物车时的同步函数,我们是在mutations.js中定义的;Vuex允许定义getter从store中的state中派生出一些状态(计算属性),如从商品和品牌数组状态中派生出根据id查询到的状态,我们是在getters.js中定义的;另外,我们的mutation-types.js中使用常量替代mutation事件类型以方便多人协作。在任意组件中访问store的计算属性时可使用mapState和mapGetters辅助函数,分发Action、Mutation时使用mapAction、mapMutation辅助函数。它们本质是都是语法糖,将store中的state、getter映射到局部计算属性,或将mutations和actions映射到局部方法,从而避免使用this.$store的方式来访问相应的函数。当然,并不是所有情况下都能使用辅助函数,如created生命周期钩子函数中或条件语句中。请注意,使用Vuex并不意味着我们需要将所有的状态放入Vuex。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。我们应该根据应用开发需要进行权衡和确定。如CartControl组件中的count状态和每个cart组件绑定不能单例化,因此不需要用放入Vuex来管理。
├─store
│ actions.js
│ getters.js
│ index.js
│ mutation-types.js
│ mutations.js
7. 如下面的文件路径树所示,本项目plugins文件夹中有两个插件。插件会为Vue.js添加全局功能,本质上就是封装更好,复用性更强的组件,Vuex和vue-route就是这样两个优秀的插件。如果我们想在任意组件中弹出模态框和信息提示、控制加载动画等,怎么办?每个组件都加一个布尔控制变量肯定导致代码冗余难以维护。用Vuex集中在根组件中处理也好不到哪里去,因为要写很多代码来判断事件类型。所以我们得自己开发插件,感兴趣的话可参考vue-dialog和vue-toast的代码自行开发一个Loading插件。那样就可以在任意组件中控制加载动画了。├─plugins
│ ├─vue-dialog
│ │ │ index.js
│ │ │ VueDialog.vue
│ │ └─themes
│ │ default.css
│ │ ios.css
│ └─vue-toast
│ │ index.js
│ └─components
│ Notification.vue
│ VueToast.vue
8. 当我们build好代码,在IE或手机自带的Android浏览器上运行时,发现添加商品到购物车的功能用不了。报错:Vuex requires a Promise polyfill in this browser,原来Vuex依赖Promise。如果浏览器并没有实现Promise,那么可以使用一个 polyfill的库,例如babel-polyfill。第一步: 安装npm install --save babel-polyfill第二步:在webpack.config.js文件中,将entry的代码改为如下:entry: { app: ["babel-polyfill", "./src/main.js"] }或者将下列代码添加到使用Vuex之前的一个地方:import 'babel-polyfill'
三、有待实现和改进功能:
1. 登录注册模块
2. 会员模块
3. 支付模块
4. 列表搜索排序功能
5. 图片列表的懒加载
6. 轮播图效果
7. 加入购物车动画
8. 页面切换动画
Vue.js devtools是一个很好的测试Vue.js应用的工具,对Vuex也支持得很好,值得拥有。