发布订阅模式
发布订阅模式我们都用过,也都尝试去实现过这样的一个EventHandler,但是这里想再重点强调的是如何实现 先发布后订阅的功能? 为什么我们需要支持先发布后订阅的功能呢?
实际开发过程中我们遇到过的先发布后订阅的场景为:我们有一个展示用户信息的头部导航模块,这个模块依赖于获取用户信息的模块来显示用户头像,但是获取用户信息是异步的,在头部信息trigger之后,我们的头部模块才加载完并listen(我们的模块用了惰性加载)。
为了满足这个需求,我们需要建立一个存放离线事件的堆栈,当事件发布的时候,如果此时还没有订阅者来订阅这个事件,我们可以暂时把发布事件的动作与参数包裹在一个函数里面,这些包装函数将被存入堆栈中,等有对象来订阅此事件的时候,我们再遍历堆栈且依次执行这些包装函数。
(function(root, factory) {
root.Event = factory();
})(window, function() {
var Event;
Event = (function() {
var namespaceCache = {},
_shift = Array.prototype.shift,
_unshift = Array.prototype.unshift,
_slice = Array.prototype.slice,
_default = 'default',
self = this;
var each = function(ary, fn) {
if(!ary) return;
var ret;
for(var i = 0, l = ary.length; i < l; i++) {
var n = ary[i];
ret = fn.call(n, i, n);
}
return ret;
}
var _remove = function(key, cache, fn) {
if(cache[key]) {
if(fn) {
cache[key].forEach(function(val, index){
console.log(val);
console.log(fn);
console.log(val === fn);
if(val === fn) {
cache[key].splice(index, 1);
}
});
}else{
cache[key] = [];
}
}
}
var _trigger = function() {
var cache = _shift.call(arguments),
key = _shift.call(arguments),
args = arguments,
_self = this,
ret,
stack = cache[key];
if(!stack || !stack.length) {
return;
}
stack.forEach(function(fn, index) {
fn.apply(_self, args);
});
};
var _create = function(namespace) {
namespace = namespace || _default;
var cache = {}, // 缓存回调事件
offlineStack = [], // 离线事件
ret = {
listen: function(key, fn, last) {
cache[key] = cache[key] || [];
cache[key].push(fn);
offlineStack.forEach(function(fn, index) {
return fn();
});
offlineStack = null;
},
trigger: function() {
var fn,
args,
_self = this;
// 要把cache 加到参数中,后面闭包里面的函数要用
_unshift.call(arguments, cache);
args = arguments;
// 这里是核心,利用闭包存下回调与当时传入的参数
fn = function() {
return _trigger.apply(_self, args);
}
if(offlineStack) {
return offlineStack.push(fn);
}
return fn();
},
remove: function(key, fn){
_remove(key, cache, fn);
}
};
// 主要就是为了能够使用对应namespace下的cache
return namespace ? (namespaceCache[ namespace ] ? namespaceCache[ namespace ] : namespaceCache[ namespace ] = ret) : ret;
};
return {
create: _create,
listen: function(key, fn, last) {
var event = this.create();
event.listen(key, fn, last);
},
trigger: function() {
var event = this.create();
event.trigger.apply( this, arguments );
},
remove: function(key, fn) {
var event = this.create();
event.remove(key, fn);
}
};
})();
return Event;
})复制代码
中介者模式
中介者模式就是用来帮助我们把复杂的层级关系铺平为一对多的简单关系
使用中介者模式以后,可以帮助我们让原本按照树型结构传递的数据可以转换为平级的结构(本来数据就不像组件需要有树型关系)
模仿redux
我们可能在regular 会这么简易的去写
var store = new Regular();
// 发布
store.dispatch = function(action) {
switch(action.type){
case 'CHANGE_TITLE': {
this.data.title = action.data;
break;
},
case ...
}
store.$emit('change', this.data);
};
// 订阅
store.subscribe = function(listener) {
this.$on('change', listener);
}
// j.js
var J = Regular.extend({
name: 'j',
config: function() {
var self = this;
store.subscribe(function() {
self.mapState(store.data);
});
},
// 全局的数据转换到组件的数据
mapState: function(storeData) {
this.data.title = storeData.title;
this.$update();
},
changeTitle: function(title) {
store.dispatch({
type: 'CHANGE_TITLE',
data: title
})
}
}}复制代码
看看 redux 的 createStore 方法大致结构
- 我们在使用createStore方法的时候要传递一个reducer 方法得到一个store。
- 通过store.getState()我们可以拿到当前的state
- store.dispatch(action) 是唯一的一个可以改变内部state的方法,我们通过给reducer传递当前state与action,调用reducer方法获得当前的new state。并且通知所有的listener
- 通过 store.subscribe(listener) 订阅方法,那么createStore 的结构应该如下所示
const createStore = (reducer) => {
let state;
let listeners = [];
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener());
};
const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
}
};
// By the time the store is returned, we want the initial state to be populated. We're going to dispatch a dummy action just to get the reducer to return the initial value.
dispatch({}); // dummy dispatch
return { getState, dispatch, subscribe };
};复制代码
最后让我们看一个完整的使用Redux的计数器的小例子:
// reducer
const counter = (state = 0, action) {
switch(action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
// Counter 组件
const Counter = ({
value,
onIncrement,
onDecrement
}) => (
<div>
<h1>{value}</h1>
<button onClick={onIncrement}>+</button>
<button onClick={onDecrement}>-</button>
</div>
)
const {createStore} = Redux;
const store = createStore(counter);
const render = ()=> {
ReactDom.render(
<Counter
value = store.getState()
onIncrement={()=>{
store.dispatch({
type: 'INCREMENT'
});
}}
onDecrement={()=>{
store.dispatch({
type: 'DRECEMENT'
})
}}
/>,
document.getElementById('root')
);
};
store.subscribe(render);
render();复制代码