Vuejs2.x 的逻辑复用与状态管理
状态管理
在平常的业务开发中,常把应用状态划分为以下三种:
- 全局状态:用于管理SPA应用全局状态,一直保留在应用生命周期内
- 组件状态:用于管理组件内部状态,随组件销毁而销毁
- 页面级状态:用于管理当前页面下所有模块的状态,随页面销毁而销毁
全局状态的管理
- 使用
vuex
来管理全局状态
├── components
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
- 模块定义与 store申明
产品模块: modules/products
// initial state
const state = () => ({
all: []
})
// getters
const getters = {}
// actions
const actions = {
getAllProducts ({ commit }) {
shop.getProducts(products => {
commit('setProducts', products)
})
}
}
// mutations
const mutations = {
setProducts (state, products) {
state.all = products
},
decrementProductInventory (state, { id }) {
const product = state.all.find(product => product.id === id)
product.inventory--
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
Store声明, 应用全局共享一个 Vuex.Store
实例
import Vue from 'vue'
import Vuex from 'vuex'
import cart from './modules/cart'
import products from './modules/products'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
cart,
products
},
}
基本使用: components/icon ProductList.vue
import { mapState, mapActions } from 'vuex'
export default {
computed: mapState({
products: state => state.products.all
}),
methods: mapActions('cart', [
'addProductToCart'
]),
created () {
this.$store.dispatch('products/getAllProducts')
}
}
组件内部状态管理
使用组件的 data
来管理
export default {
data(){
return {
isShow: false,
count: 10,
groceryList: [
{ id: 0, text: '蔬菜' },
{ id: 1, text: '奶酪' },
{ id: 2, text: '随便其它什么人吃的东西' }
]
}
}
以上两种是最基本也是最常用的状态管理方式,下面主要介绍两种页面级的状态管理方式
页面级状态管理
动态注册状态模块来管理
使用 store.registerModule
来注册页面级状态模块
使用 store.unregisterModule
来卸载状态模块
全局注册的状态,都挂载于 this.$store
下面,
打印 this.$store
如下
可以看到 store
实例提供了动态注册和卸载状态模块的能力
应用到具体的业务
页面模块的目录结构
└── stores
└── views
└── login
├── index.vue # 页面模块
└── store.js # 产品模块
页面级状态 sotre.js
export default {
// 加上 namespaced,避免与其它模块重名
namespaced: true,
state: {
count: 0,
},
mutations: {
increment(state) {
state.count++
}
}
}
页面模块 index.vue
import { mapState, mapMutations } from 'vuex';
import counterStore from './store';
export default {
//...
computed: {
...mapState({
count: (state) => state.counter.count,
}),
},
methods: {
...mapMutations({
add: 'counter/increment'
})
}
beforeCreate(){
this.$store.registerModule('counter', counterStore)
},
beforeDestroy(){
this.$store.unregisterModule('counter', counterStore)
},
//...
}
这样动态注册的好处是,随着页面的销毁,状态对象也会从全局状态管理器中卸载掉 页面下面所有的组件都能从 store 中获取相关状态数据,不用一层一层传递 缺点是,注册的模块仍然是全局状态,如果不小心使用,可能注册同名模块,有时也可能忘记卸载模块
使用 Mixin
实现逻辑复用
另外一种常用的方式就是使用类似于继承的方式,使用混入来复用逻辑
这种方式的好处是,每个vue组件的使用是独立的
混入的方式使得组件继承了所有的属性,多个组件使用同一 mixin
时互不影响,
也能跟随页面或组件的生命周期销毁而销毁
目录结构
└── mixins
└── counter.mixin.js
└── views
└── login
├── index.vue # 页面模块
// counter.mixin.js
export default {
data(){
return {
count: 0,
};
},
methods: {
increment(state) {
state.count++
}
}
}
// login/index.vue
import counter from '@/mixins/counter.mixin'
export default {
mixins: [counter],
mounted(){
console.log(this.count)
console.log(this.increment)
}
}
例:混入响应式断点
在管理平台的业务开发中一般组件库都提供了响应式的组件,但有时候我们不得不根据当前屏幕的宽度来调整一些业务逻辑
breakpoint.mixin.js
在需要的页面的混入到组件中,可实现通过 watch
状态属性 breakpointName
来处理相应的业务逻辑
export default {
data: function(){
return {
breakpointName: 'md',
}
},
mounted () {
this.getResponseName();
window.addEventListener('resize', this.getResponseName);
},
beforeDestroy () {
window.removeEventListener('resize', this.getResponseName);
},
methods: {
getResponseName() {
const options = [
{ rule: '414px', size: 'xs' },
{ rule: '768px', size: 'sm' },
{ rule: '992px', size: 'md' },
{ rule: '1200px', size: 'lg' },
];
const toMedia = rule => `(min-width: ${rule})`;
options.forEach(v => {
v.rule = toMedia(v.rule);
});
const res = options.reverse().find(v => window.matchMedia(v.rule).matches);
const responseName = res ? res.size : 'lg';
this.breakpointName = responseName;
},
}
}
使用
import breakpoint from '@/mixins/breakpoint.mixin'
export default {
mixins: [breakpoint],
mounted(){
console.log(this.breakpointName)
},
watch: {
breakpointName(n, o){
if(n !== o){
// todo
}
},
}
}
}
可以看出来,mixin
是逻辑复用利器,但唯一的缺点就是,当记不清当前 mixin
中所有的方法属性时会不小心覆盖,所以 mixin
一般跟随项目,如果做为第三方公共包,一定要提供详尽的文档,不然就只能翻源码了。
混入仍然不解决不了状态传递的问题,业务中还是需要一层层传递。
更复杂的更通用的业务,比如:列表展示,在管理平台中基本的CRUD逻辑都是一样的,可以封装如 crud.mxin.js
应用到各个页面,减少重复业务逻辑的编写,同时也便于集中管理维护升级CRUD通用逻辑,大大减少代码开发工作量。
本文使用 mdnice 排版