初次接触vue,刷完了堪称经典的vue官网文档+vue-router文档+vuex文档+vue-cli文档,然后就开始刷项目了。这篇文章总结了项目实践的一些思路。
首先看下项目的总览图(mockplus制作)
这个项目我负责的部分是一个控制台的需求,用户可以在这个模块进行佣金、订单等项目的修改设置,可以看到第1张图相当于控制台首页,页面上有1、2、3、4、5个入口,其中1-4入口进入的页面对应4个页面,但是4个页面主要结构相同,中间商品列表样式信息不同,为了简略4图归为第二张图。入口5对应第三张图。
这几个页面需求并不复杂,大项目本身也是是基于zepto的,单单把这个模块拿出来抽离成单页形式主要为了两点考虑:
控制台毕竟涉及操作dom的需求会很多,为了兼容未来产品迭代的复杂需求。
控制台从产品最终的形态上更像是一个独立的应用,且用户进入控制台首页后所有的操作就在当前单页内进行操作,无需a链接跳转,在体验上也是很贴合用户的。
基于以上两点使用vue以及vue大礼包编写了这个单页模块。
由入口而知一共有6个页面(包含控制台首页),虽然是单页模块,也是必须要符合通过url地址直接进入对应分页的需求。在切换的过程中,留下url路由信息也方便用户进行后退操作。
首先是顶级路由的配置,六个页面(组件)分别设置六个顶级路由。
// 在App.vue文件中设置<router-view> <template> <div id="app"> <router-view keep-alive :style="{'padding-bottom': paddingBottom + 'rem'}"></router-view> </div> </template>
// 在main.js中配置路由 router.map({ '/': { component: Index, name: 'index' }, '/commission': { name: 'commission', component: Commission }, '/order': { name: 'order', component: Order }, '/inventory': { name: 'inventory', component: Inventory }, '/shop': { name: 'shopList', component: ShopList }, '/qcode': { name: 'qcode', component: Qcode } })
由上图具体的佣金页面(组件)可以看到,这个页面有两个主要操作,一:搜索,根据搜索结果展示商品条目;二:翻页,根据页数展示商品条目。
原始的思路是搜索与翻页都在当前页面(组件)操作,将异步获取的数据替换当前页面的items数组。Vue会将变化的数据与view绑定,同步刷新view页面。
这样做有个缺陷,任何操作(搜索、翻页)都不会留下可追溯的路径,假设有个场景:
用户翻n页,发现了一款商品点击进入详情页(外链,属于大项目中的页面),用户返回佣金设置页面时发现又从第一页开始浏览了。
基于此类场景结合vue-router的路由查询参数功能可以换一种实现方式。
点击搜索不再在当前页面(组件)异步请求数据,而是通过$route对象进行路由跳转。
this.$route.router.go({ name: 'index', query: { keyWord: 'searchWorld', page: 1 } })
根据上图,搜索n次,或者是点击翻页n次,都会改变当前的url的查询参数。实际上改变路由查询参数,就相当于重新进入一次当前页面(组件),Vue会识别计算是否重用当前组件,这种情况<router-view>并不会产生切换效果,因为即使查询参数变化,当前页面组件始终都是 component: Commission
同一个组件。
这样设计就留下了一系列的路径,可供历史回退。
那么数据该如何获取呢?
vue-router
有一个「 切换控制流水线 」的概念,即在不同路由切换的过程中会有不同的钩子函数可以调用。
其中 data
钩子函数不管组件是否可以重用,在每次路由切换的时候都会触发。
route: { data (transition) { this.$http.get('/api/test/test', { params: { keyword: this.keyWord, page: this.currentPage, pageSize: this.numberPerPage } }).then((response) => { transition.next({ items: response.json().data.item.items, listNumber: response.json().data.item.totalNum }) }, (response) => { // error }) } },
这样实现了数据的获取,参数部分依靠当前组件的 $route
对象获取。
上文已经提到的6大分页其实就是6个组件,但是为了在开发环境下区分资源,将这6个组件放在了views文件下内。
| |-src | | | |-components | |-views | | |-Index.vue | | |-Commission.vue | | |-Order.vue | | |-...
由上图可知可以将1.title 2.搜索栏 3.confirm 4.toast 5.分页器 6.loading等待封装成全局组件。在 main.js
中进行注册。
import Toast from './components/common/Toast' Vue.component('c-toast', Toast); ... ...
商品列表也可以抽离成组件,但是在每个分页里的商品列表是不同的,所以每个分页里的商品列表都独立抽离成组件,并注册在对应的分页组件里。
import ListCommission from '../components/ListCommission' export default { name: 'commission', components: { ListCommission } }
控制台这个模块状态并不复杂,多数状态的传递都只发生在父组件和子组件这种上下层级的关系之间。
比如Commission组件(佣金分页)中的ListCommission组件(佣金页的商品列表组件)之间的状态传递就只发生在这两级之间。
// Commission.vue <template> <list-commission :visible="listNumber > 0" v-if="!$loadingRouteData" :items="items"></list-commission> </template>
其中 items
状态属于父组件,会传递到 ListCommission
组件内供其view展示。这种状态称为「组件本地状态」,组件本身管理自己的状态。
但是现在有这么一个功能点,生成二维码模块需要使用一个完整url路径,这个路径需要根据测试、线上环境的不同对应 .net/.com
。这个状态也许还有许多不同层级的组件需要使用,那么这样的状态就适合用vuex去管理。
1.首先在store中定义状态初始值
const state = { suffix: '.net' }
2.在根组件App.vue中触发action
let suffix = window.location.hostname.indexOf('showjoy.net') > -1 ? '.net' : '.com'; export const setGlobalSuffix = function ({dispatch}, suffix) { dispatch('SET_GLOBAL_SUFFIX', suffix); } this.setGlobalSuffix(suffix);
3.触发dispatch
SET_GLOBAL_SUFFIX (state, suffix) { state.suffix = suffix; }
4.get状态
export function getSuffix (state) { return state.suffix; }
只要在相应组件中定义了getSuffix的getters,就可以在相应的组件中调用这个函数,获取suffix状态。此时suffix状态可称为「应用层级状态」。应用层级状态不属于任何特定的组件,但每个组件都可以监视其变化并响应式的更新DOM。
suffix
状态只是一个简单的例子,像上图所示,当同级组件之间或者是不同直系关系的父子组件之间需要进行状态的变更,依赖「组件本地状态」将难以维护。
比如 Commission组件
需要改变 Order组件
的一个状态,如果不借助vuex,那么需要显示的编写事件将状态分发到上层组件App,Order组件需要监听这个事件。状态变更一多,那维护将是噩梦。