vubx
Vue状态管理,采用面向对象风格的api设计,灵感来自mobx
索引
vubx介绍
vubx
可以视为vuex
的面向对象风格版本,提供以下功能:
1.state
、getters
、mutation
,其概念与vuex
基本相通,区别是vubx是以class(类)和decorator(装饰器)的形式来实现的。
2.简单的依赖注入,用于解决子模块之间共享数据的问题,并支持懒加载,此功能不仅能在状态管理中使用,也可与Vue的provide/inject配合使用。(此功能主要参考InversifyJS的api设计)
3.插件化:模块插件及全局插件。
4.mutation
中间件:支持模块中间件及全局中间件。
4.支持严格模式,开启后state
只能在mutation
中被修改。
5.支持vue
官方devtool,可以在devtool的vuex标签下查看vubx
的state
、getters
、mutation
。
6.同时支持Typescript
和ECMAScript
,使用TypeScript体验最佳,起初就是专门为Vue+Typescript设计的。
安装
注意:
1.TypeScript用户需要开启tsconfig.json中的experimentalDecorators
和allowSyntheticDefaultImports
的编译选项
2.javaScript+Babel用户需要babel-plugin-transform-decorators-legacy插件,以支持ECMAScript stage 1 decorators
基本使用
此例子TypeScript
和javaScript
均可运行
import Vue from 'vue';
import { createDecorator, Service, mutation } from 'vubx';
const observable = createDecorator(Vue);
@observable({
root: true,
identifier: 'root',
strict: true,
devtool: true
})
class Addition extends Service {
a = 0;
b = 1;
get sum() {
return this.a + this.b;
}
@mutation
change() {
const temp = this.sum;
this.a = this.b;
this.b = temp;
}
}
const addition = new Addition();
new Vue({
el: '#app',
template: `<div>{{addition.sum}}</div>`,
computed: {
addition() {
return addition;
}
},
mounted() {
setInterval(() => {
this.addition.change();
}, 1000);
}
});
依赖注入
以下例子均使用TypeScript,将其接口、类型、泛型代码部分去掉既可在javaScript中使用。
基本例子
import Vue from 'vue';
import { createDecorator, Service, mutation, lazyInject, bind, IService, created, IVubxDecorator } from 'vubx';
import component from 'vue-class-component';
const observable: IVubxDecorator = createDecorator(Vue);
const moduleKeys = {
root: 'root',
A: 'moduleA',
B: 'moduleB'
};
interface IModule extends IService {
text: string;
}
@observable()
class ModuleA extends Service implements IModule {
text = 'A';
}
@observable()
class ModuleB extends Service implements IModule {
text = 'B';
}
@observable({
root: true,
strict: true,
devtool: true,
identifier: moduleKeys.root,
providers: [
bind<IModule>(moduleKeys.A).toClass(ModuleA),
bind<IModule>(moduleKeys.B).toClass(ModuleB)
]
})
class Root extends Service {
@lazyInject(moduleKeys.A)
public moduleA: IModule;
@lazyInject(moduleKeys.B)
public moduleB: IModule;
}
const rootModule = new Root();
@component({
template: '<div>{{text}}</div>',
inject: {
moduleA: moduleKeys.A,
moduleB: moduleKeys.B
}
})
class App extends Vue {
moduleA: IModule;
moduleB: IModule;
get text() {
return this.moduleA.text + this.moduleB.text;
}
}
new Vue({
el: '#app',
provide: rootModule.getProvide(),
render: h => h(App)
});
注册模块
注册类
bind<IModule>(moduleKeys.A).toClass(ModuleA)
注册值
bind<IModule>(moduleKeys.A).toValue(new ModuleA())
注册工厂
bind<IModule>(moduleKeys.A).toFactory(() => new ModuleA())
bind<IModule>(moduleKeys.B).toFactory((moduleA: IModule, moduleB: IModule) => {
return new ModuleC(moduleA, moduleB)
}, [moduleKeys.A, moduleKeys.B])
bind<IModule>(moduleKeys.B).toFactory((moduleA: IModule, moduleB: IModule) => {
if (isIOS) {
return moduleA
} else {
return moduleB
}
}, [moduleKeys.A, moduleKeys.B])
单例与多例
默认为单例注册,支持多例但不建议使用,单一值注册(toValue)不支持多例
bind<IModule>(moduleKeys.A).toClass(ModuleA).inSingletonScope()
bind<IModule>(moduleKeys.A).toClass(ModuleA).inTransientScope()
bind<IModule>(moduleKeys.A).toFactory(() => new ModuleA()).inTransientScope()
在模块中注入其他模块
@observable()
class Module extends Service {
@lazyInject(moduleKeys.A)
public moduleA: IModule;
@lazyInject
public moduleB: IModule;
}
在Vue组件中注入模块
Vue已经提供provide/inject功能,可以很方便的注入vubx模块
const rootModule = new Root()
new Vue({
el: '#app',
provide: rootModule.getProvide(),
render: h => h(App)
});
@component({
template: '<div>{{text}}</div>',
inject: {
moduleA: moduleKeys.A,
moduleB: moduleKeys.B
}
})
class App extends Vue {
moduleA: IModule;
moduleB: IModule;
get text() {
return this.moduleA.text + this.moduleB.text;
}
}
若使用Vue Property Decorator或者使用自定义的装饰器,也可如下例所写,更加简洁
@component({
template: '<div>{{text}}</div>'
})
class App extends Vue {
@Inject(moduleKeys.A)
moduleA: IModule;
@Inject()
moduleB: IModule;
get text() {
return this.moduleA.text + this.moduleB.text;
}
}
插件与mutation中间件
基本例子
以下是简单的缓存例子
import Vue from 'vue';
import { createDecorator, Service, mutation } from 'vubx';
const observable = createDecorator(Vue);
const cacheKey = 'cache-key';
const plugin = (store: Counter) => {
store.subscribe({
after: () => {
localStorage.setItem(cacheKey, JSON.stringify(store.$state));
}
});
};
@observable({
root: true,
identifier: 'counter',
strict: true,
devtool: true,
plugins: [
plugin
]
})
class Counter extends Service {
num = 0;
@mutation
add() {
this.num++;
}
init() {
const cacheStr = localStorage.getItem(cacheKey);
if (cacheStr) {
const cache = JSON.parse(cacheStr);
this.replaceState(cache);
}
setInterval(() => {
this.add();
}, 1000);
}
}
const addition = new Counter();
new Vue({
el: '#app',
template: `<div>{{addition.num}}</div>`,
computed: {
addition() {
return addition;
}
},
mounted() {
addition.init();
}
});
插件详解
vubx
的插件分为模块插件
与全局插件
,上述的缓存例子便是一个简单的模块插件
,只会在注册的本模块初始化时执行,当项目有很多模块时,可以选择性的给某些模块加入缓存机制。
如下注册全局插件
,注意globalPlugins选项只在root模块
中有效,并且全局插件
会在providers选项下注册的所有模块被第一次注入时执行,对于root模块
只会在初始化时执行,模块插件
的执行时机也与其一致。
@observable({
root: true,
identifier: 'counter',
strict: true,
devtool: true,
plugins: [
plugin
],
globalPlugins: [
createLoggerPlugin()
]
})
class Counter extends Service {
}
mutation中间件详解
在严格模式下,vubx
中的state
只能通过mutation方法改变,mutation中间件的功能就是可以在mutation
方法执行前和执行后进行一系列统一的业务操作,其实也可以说是实现了AOP
的模式。
这是一个简单的例子:在mutation
方法执行前打印mutation
信息,在其执行后缓存模块的状态到localStorage
store.subscribe({
before: (mutation: IMutation, service: IService) => {
console.log(`
type: ${mutation.type}
payload: ${JSON.stringify(mutation.payload)}
mutationType: ${mutation.mutationType}
identifier: ${mutation.identifier}
`);
},
after: (mutation: IMutation, service: IService) => {
localStorage.setItem(cacheKey, JSON.stringify(service.$state));
}
});
vubx的mutation定义如下
interface IMutation {
type: string;
payload: any[];
mutationType: string;
identifier: IIdentifier;
}
全局中间件的订阅则使用subscribeGlobal
方法,使用方式和subscribe
一致,但只有root模块
可以调用此方法,全局中间件会作用于在root模块
中注册的所有模块。
生命周期
由于vubx
的实现机制是创建子类代替父类的引用,并在子类中做代理Vue数据和依赖注入信息的初始化,因此在父类的构造函数中是无法读取注入进来的模块,但vubx提供了初始化成功的调用钩子用于替代构造函数。
@created()
init() {
}
@created(['moduleA','moduleB'])
init(moduleA , moduleA) {
}