本文主要是介绍Vue 初接触实战之账单组件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Vue作为一个构建数据驱动web界面的库,是去年最火的MVVM风格库之一。Vue的用起来有Angular的影子,把很多自定义指令注入html,又吸收了React的优点和精华。比如与Vue的配套使用的Vuex就是从Redux和Flux中借鉴了不少思路。
Vue从去年的下半年开始火起来,目前在Awesomes前端资源库的热度排行里面处于Top2的位置。得益于更加简洁的语法和易用性,Vue目前在社区的受欢迎度不在React之下。很多公司纷纷上手Vue了,我厂的前端团队也已经在使用Vue进行业务重构了。即将发布的Vue2.0版本也采用了和React一样的Virtual Dom技术,而且推出了前后端同构的服务端渲染框架vue-hackernews-2.0,这将会使得Vue在技术社区更加受追捧。目前Vue2.0的RC文档也已经出来了,可以预期,它的使用前景将会非常非常不错。
这里要讲的是啥?
这篇文章不是普及Vue的内部实现原理,也不是要比较MVVM三大法器Angular、React和Vue的优劣。这里主要是对自己最近上手学习Vue,进行SPA开发过程中的思路总结。希望通过展示一个分期账单组件,可以对像我一样的Vue初学者提供一丁点的帮助。因为是Vue新手,如有错误之处,也欢迎各位大神批评指正。
Vuex
首先要介绍的是Vuex这个神器。关于Vue的基本语法,可以直接打开官方1.0文档学习。当然,如果你想直接上手Vue2.0,也可以直接访问这里。
Vuex 是一个专门为 Vue.js 应用所设计的集中式状态管理架构。它借鉴了 Flux 和 Redux 的设计思想,但简化了概念,并且采用了一种为能更好发挥 Vue.js 数据响应机制而专门设计的实现。
也就是说,Vuex将父组件与子组件之间的props传递,组件与组件之间的消息传递集中起来管理。在一个小型应用中,我们可能不会用到Vuex,这样会把原本很简单的任务复杂化了。但是,企业级的项目都是多条业务线交叉的,如果单纯使用Vue本身的组件通信,业务组件之间复杂的关系网会让项目后期的调试和Bug跟踪非常困难,尤其是当你在构建一个SPA项目的时候。
为了更好的解决在大型应用中状态的共用问题,我们需要对组件的 组件本地状态(component local state) 和 应用层级状态(application level state) 进行区分。应用级的状态不属于任何特定的组件,但每一个组件仍然可以监视(Observe)其变化从而响应式地更新 DOM。通过汇总应用的状态管理于一处,我们就不必到处传递事件。
Vuex的数据流模型如下图所示:
- 用户在组件中的输入操作触发 action 调用;
- Actions 通过分发 mutations 来修改 store 实例的状态;
- Store 实例的状态变化反过来又通过 getters 被组件获知,组件获悉状态变更之后就是数据驱动的魔法——实时更新DOM状态。
需要注意的一点是,mutation本身是一个事件系统,通过定义事件来触发Store的状态变更。mutation里面定义的函数必须是同步函数,涉及到API调用的逻辑要放到Action进行,因为Action是可以定义异步函数的。
Vue-route
介绍完Vuex,我们来说说vue-router。vue-router是Vue官方提供的路由插件,通过vue-router配合Vuex,我们可以非常高效地开发大型SPA。vue-router最基本的作用是做SPA路由映射。
1 2 3 4 5 6 7 8 | router . map ( { '/foo' : { component : Foo } , '/bar' : { component : Bar } } ) |
上面的配置中,当访问路径”/foo”的时候,SPA就会在<router-view>挂载组件Foo,改变访问路径为 “/bar”,Bar组件就会切换到主视口。这就是一个最基本的SAP路由配置。
一步一步使用Vue构建一个SPA账单组件
在简单了解了Vuex和Vue-router的基本概念之后,我们可以进入实践环节。如果还没有完全理解清楚Vue的语法和Vuex数据流的概念,可以继续多看几次官方文档,尤其是对刚接触MVVM的人来说,可能要多看几次才能对数据驱动的编程理念有更好的理解。
1、项目目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | | - vue - demo | - node_modules | - order | - app | - components | - All . vue | - Latest . vue | - Nav . vue | - Order . vue | - vuex | - modules | - orderList . js | - action . js | - mutation . js | - mutation - type . js | - store . js | - App . vue | - main . js | - router . js | - index . html | - webpack . config . js | - package . json | - README . md |
这个是比较推荐的项目目录结构。其中order是我们要展示的账单组件的根目录,组件目录划分为入口index.html、webpack配置文件和app文件夹。在app目录内:
- App.vue – 组件的根节点;
- main.js – 组件入口;
- router.js – 路由相关配置;
- component – 组件根节点下的各个子组件;
- vuex – 顾名思义,数据流架构vuex相关的文件,包括action、mutation和store。
在大型项目中,组件数量多,涉及的数据流是比较复杂的。为了更好地管理store,我们又把store定义在不同的模块中,比如modules目录下的orderList就是账单列表相关的数据流。事件类型比较多的情况下,我们把事件名称定义在mutation-type中。
2、创建组件入口
组件入口需要做的事情包括创建Vue组件实例、挂载插件、配置路由,main.js如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import Vue from 'vue' import VueRouter from 'vue-router' import ConfigRouter from './router' import App from './App.vue' // import './style/order.css' Vue . config . devtools = true Vue . use ( VueRouter ) // 创建vue-router实例 var router = new VueRouter ( ) //配置路由 ConfigRouter ( router ) // 滚动到页面顶部 router . beforeEach ( function ( ) { window . scrollTo ( 0 , 0 ) } ) //全路径匹配,防止出现404 router . redirect ( { '*' : '/' } ) //启动APP router . start ( App , '#root' ) |
3、使用vue-router配置SPA路由
账单组件的路由包括“最近7天待还”以及“全部待还”,router.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | export default function ( router ) { router . map ( { '/' : { component : require ( "./components/Latest.vue" ) , linkActiveClass : 'active' } , '/latest' : { component : require ( "./components/Latest.vue" ) , linkActiveClass : 'active' } , '/all' : { component : require ( "./components/All.vue" ) , linkActiveClass : 'active' } } ) } |
4、创建 Vuex Store
尝试列出组件用到的所有数据,在vuex目录下创建store.js文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // vuex/store.js import Vue from 'vue' import Vuex from 'vuex' // 导入各个模块的初始状态和 mutations import orderList from './modules/orderList' Vue . use ( Vuex ) export default new Vuex . Store ( { // 组合各个模块 modules : { orderList } } ) |
由于我们把store拆分到不同的模块,所以创建store实例的时候需要引入orderList模块,它包括账单列表orders对象和当前被激活的账单对象activeOrder。需要定义的mutation只有一个”SHOW_DETAIL”,当用户点击账单列表的某一个账单的时候,SHOW_DETAIL更新store的activeOrder,表示当前被展开的账单对象。modules/orderList.js如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | // vuex/modules/index.js import { SHOW_DETAIL , } from '../mutation-types' // 该模块的初始状态 const state = { orders : [ { id : 'aedf9c25' , tradeDate : '2016.04.08' , name : 'Nike跑步鞋青年版' , totalPrice : 888 , repayDate : '2016.08.08' , capital : '880' , interest : '5.5' , extra : '2.5' , type : '消费' , currentTerm : 1 , totalTerms : 1 } , { id : 'cves9chs' , tradeDate : '2016.04.10' , name : '支付宝提现' , totalPrice : 773.5 , repayDate : '2016.08.10' , capital : '769' , interest : '4.5' , extra : '0' , type : '现金' , currentTerm : 1 , totalTerms : 4 } , { id : 'deef1d3g' , tradeDate : '2016.05.15' , name : '喜洋洋抱枕' , totalPrice : 204.8 , repayDate : '2016.08.15' , capital : '203.5' , interest : '1.3' , extra : '0' , type : '消费' , currentTerm : 2 , totalTerms : 3 } ] , activeOrder : { } } // 相关的 mutations const mutations = { [ SHOW_DETAIL ] ( state , id ) { state . activeOrder = state . orders . find ( function ( ele ) { return ele . id == id ; } ) ; } } export default { state , mutations } |
mutation-types.js:
1 | export const SHOW_DETAIL = 'SHOW_DETAIL' |
5、Action
Actions 是组件内用来分发 mutations 的函数。第一个参数固定为store。在这里,当用户点击账单列表的某一个账单区域的时候,要调用dispatch(‘SHOW_DETAIL’)。这里的demo只涉及到一个简单的用户事件,所以action.js也比较简单:
1 2 3 4 5 6 7 8 | // index actions export const showDetail = makeAction ( 'SHOW_DETAIL' ) function makeAction ( type ) { return ( { dispatch } , . . . args ) = > dispatch ( type , . . . args ) } |
6、Vue组件
我们在第一步,创建入口文件main.js的时候import进来App.vue这个根组件:
1 2 3 | import App from './App.vue' . . . router . start ( App , '#root' ) |
定义App.vue也很简单,创建一个Vue实例,把vuex的store注入到根实例即可,这样组件内的所有子组件都能访问到store:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // App.vue <script> import Nav from './components/Nav.vue' import store from './vuex/store' export default { name : 'App' , // 注入store到根组件 store , data ( ) { return { } } , components : { //子组件order-nav 'order-nav' : Nav } } </script> < template > < div id = "app" > < order - nav > < / order - nav > < router - view > < / router - view > < / div > < / template > |
这里的order-nav是一个导航栏组件,包括“最近7天待还”和“全部待还”两个,由于”全部待还”的展示和“最近7天待还”基本一样,demo里面就没有再做实现,只提供一个占位方便展示router切换。Latest.vue就是最近7天待还子组件,它获取近7天待还账单列表,并在下一级子组件(order)中渲染组件列表。组件获取store的数据是通过getter来实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | // Latest.vue <script> import { showDetail } from '../vuex/actions' ; import Order from './Order.vue' ; export default { name : 'Latest' , data ( ) { return { } } , components : { 'order' : Order } , vuex : { //解构函数,{orderList}对象指的是store的orderList模块的state.orderList getters : { orders : ( { orderList } ) = > orderList . orders } , //注入actoin actions : { showDetail } , computed : { } } } </script> < template > < div class = "container-fluid" > < ul class = "list-unstyled row" > < order > < / order > < / ul > < / div > < / template > |
Order.vue是这个账单组件中负责内容显示和用户事件分发的组件模块。Order组件中包括比较详细的Vue指令语法,比如v-for,v-show,track-by等。为了增强用户体验,Order组件在用户点击账单展示详情的时候,通过定义trasitoin属性达到简单的动画切换效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | // Order.vue <script> import { fold , showDetail } from '../vuex/actions' export default { name : 'Order' , vuex : { getters : { orders : ( { orderList } ) = > orderList . orders , activeOrder : ( { orderList } ) = > orderList . activeOrder } , actions : { showDetail } } } </script> < template > < li class = "bill" v-for = "order in orders" @ click = "showDetail(order.id)" track-by = "id" > < div > < h4 > { { order . name } } < /h4> <p>待还 <span class="text-danger">{{order.totalPrice}}</s pan >元 & nbsp ; & nbsp ; < span > < small > [ { { order . currentTerm } } /{{order.totalTerms}}]</s mall > < / span > < / p > < / div > < div class = "bill-detail" v-show = "order.id==activeOrder.id" transition = "fade" > < p > < div class = "order-item" >最后还款日期: { { order . repayDate } } < / div > < div class = "order-item" >交易类型期: { { order . type } } < / div > < div class = "order-item" >应还本金: { { order . capital } }元 < / div > < div class = "order-item" >应还利息: { { order . interest } }元 < / div > < div class = "order-item" >手续费: { { order . extra } }元 < / div > < div class = "order-item" >交易日期: { { order . tradeDate } } < / div > < / p > < / div > < / li > < / template > <style media ="screen"> .bill { border-top : 2px solid #e7e7e7 ; border-bottom : 2px solid #e7e7e7 ; margin : 5px ; padding : 10px ; cursor : pointer ; } .bill-detail { padding : 0 10px ; } .order-item { display : inline-block ; width : 45% ; } /* 过渡效果 */ .fade-transition { transition : all .8s ease ; } .fade-enter, .fade-leave { height : 0 ; opacity : 0 ; } </style> |
最后附上的是组件导航子组件,主要是负责router路由切换的Nav.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // Nav.vue <script> export default { name : 'nav' , vuex : { getters : { current : ( { orderList } ) = > orderList . activeOrder } } } </script> < template > < nav class = "navbar navbar-default" > < div class = "container-fluid" > < div class = "navbar-header" > < a class = "navbar-brand" href = "#" v-link = "{ path: '/' }" > Vue -订单 demo < / a > < / div > < div class = "collapse navbar-collapse" > < ul class = "nav navbar-nav" > < li v-link = "{ path: '/latest',activeClass:'active'}" > < a href = "#!" >近 7天待还 < span class = "sr-only" > ( current ) < / span > < / a > < / li > < li v-link = "{ path: '/all',activeClass:'active'}" > < a href = "#!" > 全部待还 < / a > < / li > < / ul > < / div > < / div > < / nav > < / template > |
至此,我们已经一步步实现了一个基于Vue+Vuex+vue-router搭建的SPA组件demo,如果大家还没学会,可以直接去把完整的demo看一遍,喜欢的话也麻烦给个star。点击这里进入账单组件的github地址。
这篇关于Vue 初接触实战之账单组件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!