Vue 初接触实战之帐单组件

Vue做为一个构建数据驱动web界面的库,是去年最火的MVVM风格库之一。Vue的用起来有Angular的影子,把不少自定义指令注入html,又吸取了React的优势和精华。好比与Vue的配套使用的Vuex就是从Redux和Flux中借鉴了很多思路。css

Vue从去年的下半年开始火起来,目前在Awesomes前端资源库的热度排行里面处于Top2的位置。得益于更加简洁的语法和易用性,Vue目前在社区的受欢迎度不在React之下。不少公司纷纷上手Vue了,我厂的前端团队也已经在使用Vue进行业务重构了。即将发布的Vue2.0版本也采用了和React同样的Virtual Dom技术,并且推出了先后端同构的服务端渲染框架vue-hackernews-2.0,这将会使得Vue在技术社区更加受追捧。目前Vue2.0的RC文档也已经出来了,能够预期,它的使用前景将会很是很是不错。html

这里要讲的是啥?

这篇文章不是普及Vue的内部实现原理,也不是要比较MVVM三大法器Angular、React和Vue的优劣。这里主要是对本身最近上手学习Vue,进行SPA开发过程当中的思路总结。但愿经过展现一个分期帐单组件,能够对像我同样的Vue初学者提供一丁点的帮助。由于是Vue新手,若有错误之处,也欢迎各位大神批评指正。前端

Vuex

首先要介绍的是Vuex这个神器。关于Vue的基本语法,能够直接打开官方1.0文档学习。固然,若是你想直接上手Vue2.0,也能够直接访问这里vue

Vuex 是一个专门为 Vue.js 应用所设计的集中式状态管理架构。它借鉴了 Flux 和 Redux 的设计思想,但简化了概念,而且采用了一种为能更好发挥 Vue.js 数据响应机制而专门设计的实现。node

也就是说,Vuex将父组件与子组件之间的props传递,组件与组件之间的消息传递集中起来管理。在一个小型应用中,咱们可能不会用到Vuex,这样会把本来很简单的任务复杂化了。可是,企业级的项目都是多条业务线交叉的,若是单纯使用Vue自己的组件通讯,业务组件之间复杂的关系网会让项目后期的调试和Bug跟踪很是困难,尤为是当你在构建一个SPA项目的时候。webpack

为了更好的解决在大型应用中状态的共用问题,咱们须要对组件的 组件本地状态(component local state) 和 应用层级状态(application level state) 进行区分。应用级的状态不属于任何特定的组件,但每个组件仍然能够监视(Observe)其变化从而响应式地更新 DOM。经过汇总应用的状态管理于一处,咱们就没必要处处传递事件。git

Vuex的数据流模型以下图所示:github

vuex数据流模型

  1. 用户在组件中的输入操做触发 action 调用;web

  2. Actions 经过分发 mutations 来修改 store 实例的状态;vue-router

  3. Store 实例的状态变化反过来又经过 getters 被组件获知,组件获悉状态变动以后就是数据驱动的魔法——实时更新DOM状态。

须要注意的一点是,mutation自己是一个事件系统,经过定义事件来触发Store的状态变动。mutation里面定义的函数必须是同步函数,涉及到API调用的逻辑要放到Action进行,由于Action是能够定义异步函数的。

Vue-route

介绍完Vuex,咱们来讲说vue-router。vue-router是Vue官方提供的路由插件,经过vue-router配合Vuex,咱们能够很是高效地开发大型SPA。vue-router最基本的做用是作SPA路由映射。

router.map({
    '/foo': {
        component: Foo
    },
    '/bar': {
        component: Bar
    }
})

上面的配置中,当访问路径"/foo"的时候,SPA就会在<router-view>挂载组件Foo,改变访问路径为 "/bar",Bar组件就会切换到主视口。这就是一个最基本的SAP路由配置。

一步一步使用Vue构建S一个PA帐单组件

在简单了解了Vuex和Vue-router的基本概念以后,咱们能够进入实践环节。若是尚未彻底理解清楚Vue的语法和Vuex数据流的概念,能够继续多看几回官方文档,尤为是对刚接触MVVM的人来讲,可能要多看几回才能对数据驱动的编程理念有更好的理解。

一、项目目录

|-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中。

二、建立组件入口

组件入口须要作的事情包括建立Vue组件实例、挂载插件、配置路由,main.js以下:

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')

三、使用vue-router配置SPA路由

帐单组件的路由包括“最近7天待还”以及“所有待还”,router.js:

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'
    }
  })
}

四、建立 Vuex Store

尝试列出组件用到的全部数据,在vuex目录下建立store.js文件:

// 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以下:

// 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:

export const SHOW_DETAIL = 'SHOW_DETAIL'

五、Action

Actions 是组件内用来分发 mutations 的函数。第一个参数固定为store。在这里,当用户点击帐单列表的某一个帐单区域的时候,要调用dispatch('SHOW_DETAIL')。这里的demo只涉及到一个简单的用户事件,因此action.js也比较简单:

// index actions
export const showDetail = makeAction('SHOW_DETAIL')

function makeAction(type) {
  return ({
    dispatch
  }, ...args) => dispatch(type, ...args)
}

六、Vue组件

咱们在第一步,建立入口文件main.js的时候import进来App.vue这个根组件:

import App from './App.vue'
...
router.start(App, '#root')

定义App.vue也很简单,建立一个Vue实例,把vuex的store注入到根实例便可,这样组件内的全部子组件都能访问到store:

// 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来实现的。

// 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属性达到简单的动画切换效果。

// 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}}</span>元&nbsp;&nbsp;
      <span><small>[{{order.currentTerm}}/{{order.totalTerms}}]</small></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:

// 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地址。

相关文章
相关标签/搜索