一次偶然在掘金看到一位大大分享了老外写的js状态管理文章,通读后就决定本身也实现一遍,目的是了解状态管理的内部机制.javascript
当前的项目多数以组件化开发,状态管理库使得组件间状态管理变得很是方便。html
这个模块其实是观察者模式,是一种一对多的依赖关系,当对象的某种状态发生改变,全部依赖它的对象都将获得通知,触发已经注册的事件.vue
在主题Subject
类中首先定义this.eventList
保存须要注册的事件,依次添加subscribe
(订阅)、unsubscribe
(取消订阅)、publish
(发布订阅)等方法java
subscribe
和unsubscribe
的两个参数:name
表明注册事件的惟一名字,fn
为事件name
的回调函数,表示全部fn
方法都注册到名为name
的集合下git
class Subject {
constructor() {
this.eventList = []
}
/** * 订阅主题 * @param {string} name 事件名称 * @param {function} fn 事件方法 */
subscribe(name, fn) {
if (!this.eventList.hasOwnProperty(name)) {
this.eventList[name] = []
}
this.eventList[name].push(fn)
console.log('this.eventList: ', this.eventList);
}
/** * 取消订阅主题 * @param {string} name 事件名称 * @param {function} fn 事件方法 */
unsubscribe(name, fn) {
var fns = this.eventList[name];
if (!fns || fns.length == 0) { // 若是没有订阅该事件,直接返回
return false
}
if (!fn) { // 若是传入具体函数,表示取消全部对应name的订阅
fns.length = 0
} else {
for (var i = 0; i < fns.length; i++) {
if (fn == fns[i]) {
fns.splice(i, 1);
}
}
}
}
/** * 发布主题,触发订阅事件 */
publish() {
var name = Array.prototype.shift.call(arguments) // 获取事件名称
var fns = this.eventList[name]
if (!fns || fns.length == 0) { // 没有订阅该事件
return false
}
for (var i = 0, fn; i < fns.length; i++) {
fn = fns[i]
fn.apply(this, arguments)
}
}
}
复制代码
对于观察者类,传入主题、事件名称、事件方法,目的是将事件注册到相应主题上:github
class Observer {
constructor(subject, name, fn) {
this.subject = subject
this.name = name
this.subject.subscribe(name, fn)
}
}
复制代码
LibStore
类核心LibStore
类须要引入上面的订阅发布模块的主题类,状态管理我的理解为一个单例化的主题,全部的状态事件都在同一个主题下进行订阅发布,所以实例化一次Subject
便可。同时须要对state
数据进行监听和赋值,建立LibStore
类须要传入参数params
,从参数中获取actions
、mutations
,或者默认为{}vuex
constructor(params){
var _self = this
this._subject = new Subject()
this.mutations = params.mutations ? params.mutations : {}
this.actions = params.actions ? params.actions : {}
}
复制代码
为了判LibStore
对象在任意时刻的状态,须要定义status
用来记录,状态有三种:app
this.status = 'resting';
this.status = 'mutation';
this.status = 'action';
复制代码
存放数据state
也会从params
传入,但为了监听LibStore
中存储的数据变化,咱们引入了代理Proxy
,使每次访问和改变state
数据变化都获得监听,改变state
数据时触发主题发布,执行全部依赖stateChange
事件的方法。函数
// 代理状态值,监听状态变化
this.state = new Proxy(params.state || {}, {
get(state, key) {
return state[key]
},
set(state, key, val) {
if (_self.status !== 'mutation') {
console.warn(`须要采用mutation来改变状态值`);
}
state[key] = val
console.log(`状态变化:${key}:${val}`)
_self._subject.publish('stateChange', _self.state)
_self.status = 'resting';
return true
}
})
复制代码
改变state
中数据经过commit
或dispatch
方法来执行组件化
/** * 修改状态值 * @param {string} name * @param {string} newVal */
commit(name, newVal) {
if (typeof (this.mutations[name]) != 'function') {
return fasle
}
console.group(`mutation: ${name}`);
this.status = 'mutation'; // 改变状态
this.mutations[name](this.state, newVal);
console.groupEnd();
return true;
}
/** * 分发执行action的方法 * @param key 的方法属性名 * @param newVal 状态的新值 */
dispatch(key, newVal) {
if (typeof (this.actions[key]) != 'function') {
return fasle
}
console.group(`action: ${key}`);
this.actions[key](this, newVal);
self.status = 'action';
console.groupEnd();
return true
}
复制代码
最后,将实例化的主题_subject
暴露出来,以便后续注册stateChange
事件时使用
getSubject() {
return this._subject
}
复制代码
LibStore
组件使用vuex
的同窗对这个组件必定不陌生,主要是配置state
、mutations
、actions
,并把参数传入核心LibStore
组件类的实例当中
import libStore from "./libStore";
let state = {
count: 0
}
let mutations = {
addCount(state, val) {
state.count = val
},
}
let actions = {
updateCount(context, val) {
context.commit('addCount', val);
}
}
export default new libStore({
state,
mutations,
actions
})
复制代码
stateChange
事件StoreChange
类将做为应用组件的继承类使用,目的是使使用组件注册stateChange
事件,同时得到继承类的update
方法,该方法将在state
数据变化时的到触发。
引入刚刚实例化LibStore
的对象store
和订阅发布模块中的观察者类,并注册stateChange
事件和回调update
方法
import store from '@/assets/lib/store'
import { Observer } from './subject'
class StoreChange {
constructor() {
this.update = this.update || function () {};
new Observer(store.getSubject(), 'stateChange', this.update.bind(this))
}
}
复制代码
实例将采用两个组件Index
和Detail
,分别表明两个页面,经过hash
路由切换挂载实现跳转,须要说明的是,每次挂载组件前须要清除已经在状态对象的单例化主题中注册的stateChange
方法,避免重复注册。
<!-- 页面art模板 -->
<div class="index">
<h1>首页</h1>
<hr>
<button id="btn1">增长数量</button>
<button id="btn2">减小数量</button>
<h3 id='time'><%= count%></h3>
</div>
复制代码
// 组件Js
import StateChange from '@/assets/lib/stateChange'
import store from '@/assets/lib/store'
export default class Index extends StateChange{
constructor($root){
super()
this.$root = $root
this.render()
document.querySelector('#btn1').addEventListener('click',this.add.bind(this))
document.querySelector('#btn2').addEventListener('click',this.minus.bind(this))
}
render(){
var indexTmpl = require('./index.art')
this.$root.innerHTML =indexTmpl({count:store.state.count})
}
update(){
document.querySelector('#time').textContent = store.state.count
}
add(){
var count = store.state.count
store.commit('addCount',++count)
}
minus(){
var count = store.state.count
store.dispatch('updateCount',--count)
}
}
复制代码
<!-- 页面art模板 -->
<div class="detail">
<h1>详情</h1>
<hr>
<h3 id="count"><%= count%></h3>
</div>
复制代码
import StateChange from '@/assets/lib/stateChange'
import store from '@/assets/lib/store'
export default class Index extends StateChange {
constructor($root){
super()
this.$root = $root
this.render()
}
render(){
var detailTmpl = require('./detail.art')
this.$root.innerHTML = detailTmpl({count:store.state.count})
}
}
复制代码
文章参考原生 JavaScript 实现 state 状态管理系统
最后感谢原文做者和分享做者! 完整代码见Github,欢迎交流和star!