从 0 实现一个 tiny-redux
讲真, redux 已经很小了,去掉注释代码也就 300 行吧,注释写的也是非常详细了. redux 更多的是对思维上的变化:数据改变 + 视图更新 二者分开,各自管理自己.
现在,让我们从无到有!!
so tiny !
redux 是这样的一个流程:触发一个 action --> redux 做一些逻辑,返回 state --> 触发监听程序. 所以一个 最小的 redux:
我们的这个 Store 和 redux 的 store 提供了想的 api:
class Store {
constructor(reducer, state = {}) {
this.state = state
this.listeners = []
this.reducer = reducer
}
dispatch(action) {
this.state = this.reducer(this.state, action)
this.listeners.forEach(listener => listener())
}
getState() {
return this.state
}
subscribe(listener) {
this.listeners.push(listener)
}
}
dispatch 触发一个 action
getState 返回当前状态
subscribe 增加一个监听器
让我们用这个最小的例子实现一个 计数器 在线地址
另一个灵魂 middleware
function reducer(state, action) {
switch (action.type) {
case 'addOne': {
return {
...state,
count: state.count + 1
}
}
default: {
return state
}
}
}
const store = new Store(reducer, {count: 0})
store.subscribe(() => {
console.log('subscribe test:', store.getState())
})
store.dispatch({type: 'addOne')
store.dispatch({type: 'addOne')
redux 的 中文文档 上关于 middleware 的部分, 已经讲的很好了.现在我们从另一个角度来看这个问题, 首先,middleware 是 redux 在 dispatch 前后,提供的扩展机制. 比如日志功能, 需要在 dispath 一个 action 之前记录一下状态,然后 reducer 处理完逻辑之后, 再次记录一下. 这不就是 面向切面编程吗! 时髦的 AOP! 用 java 的话不管是 静态代理还是动态代理, 写起来都挺复杂的. 但是 js 实现 很简单:
enhancer 方法接受一个方法 A, 返回一个增强的方法 B. 对 B 我们可以再次 增强,所以这里是可以链式调用的:
function enhancer(originF) {
return function(...args) {
console.log('before')
var result = originF(...args)
console.log('after')
return result
}
}
对于 fEnhancer(hEnhancer(gEnhancer(justPrint))) 等效的写法如下:
var fEnhancer = function (originF) {
return function (...args) {
console.log('this is fEnhancer before')
var r = originF(...args)
console.log('this is fEnhancer after')
return r
}
}
var hEnhancer = function (originF) {
return function (...args) {
console.log('this is hEnhancer before')
var r = originF(...args)
console.log('this is hEnhancer after')
return r
}
}
var gEnhancer = function (originF) {
return function (...args) {
console.log('this is gEnhancer before')
var r = originF(...args)
console.log('this is gEnhancer after')
return r
}
}
function justPrint() {
console.log('justPrint...')
}
fEnhancer(hEnhancer(gEnhancer(justPrint)))()
//这个例子输出
this is fEnhancer before
this is hEnhancer before
this is gEnhancer before
justPrint...
this is gEnhancer after
this is hEnhancer after
this is fEnhancer after
更加流弊的写法, 也就是 redux 的实现 (巧妙的使用了数组的 reduce 方法):
var enhancerArray = [gEnhancer, hEnhancer, fEnhancer]
function enhancerFun(originF) {
let of = originF
enhancerArray.forEach(enhancer => {
of = enhancer(of)
})
return of
}
回到 redux, 需要我们增强的是 dispatch, 所以只需要 enhancerFun(store.dispatch). 这里有两个问题: 第一个问题 由于我们的 dispatch 里面使用了 this, 而这个增强的调用: var r = originF() 这里就丢掉了 this.解决方法如下:
var enhancerArray = [gEnhancer, hEnhancer, fEnhancer]
function enhancerFun2(originF) {
return enhancerArray.reduce((a, b) = >(...args) = >a(b(...args)))(originF)
}
这样在任何地方调用 store 的方法, this 都没有问题了
class Store {
constructor(reducer, state) {
this.state = state
this.listeners = []
this.reducer = reducer
this.dispatch = this.dispatch.bind(this)
this.getState = this.getState.bind(this)
this.subscribe = this.subscribe.bind(this)
}
...
}
第二个问题:在 gEnhancer 里面我们想要调用 store.getState() 来记录 调用 dispatch 前后的状态怎么办? (我们不可能每次去 import store 吧, 因为在写 enhancer 的时候, 可能压根就不知道 store 在哪里呢. ) 方法如下:
通过闭包的形式, 我们让 fEnhancer 内部的逻辑 可以直接使用 getState.
var fEnhancer = function({
getState,
dispatch
}) {
return function(originF) {
return function(...args) {
console.log('this is fEnhancer before', getState()) var r = originF(...args) console.log('this is fEnhancer after', getState()) return r
}
}
}
那 middleware 是什么呢? 实际上, 这里的 fEnhancer 就是标准的一个 redux middleware, 是的,redux-logger 可以不用了, 让我们用 fEnhancer 来记录日志吧.
对应的 applyMiddleware:
现在, 给我们开头的 reducer 增强一下吧!! 在线地址
function applyMiddleware(store, ...args) {
console.log(args)
const enArr = args.map(middleware => middleware({
getState: store.getState,
dispatch: store.dispatch
}))
let of = store.dispatch
enArr.forEach(en => {
of = en(of)
})
store.dispatch = of
}
辅助函数
到这里, tineyredux 其实已经结束了. 但是 redux 为了方便开发者 提供了两个辅助函数: combineReducers 和 bindActionCreators. bindActionCreators 就是在 原本调用 actionCreator 的时候,默认帮你 dispatch 一下: actionCreator() ==》 store.dispatch(actionCreator()). 也可以理解为 '增强':
combineReducers 是为了解决另外的痛点, 比如如下的 store 和 reducer:
function bindActionCreator(creator, dispatch) {
return function(...args) {
dispatch(creator(args)) // <---- 也可以理解为 '增强'
}
}
export
default
function bindActionCreators(creators, dispatch) {
const keys = Object.keys(creators) const result = {}
keys.forEach(key = >{
result[key] = bindActionCreator(creators[key], dispatch)
}) return result
}
大部分情况, 我们发现我们的应用,clock 数据部分,对应 clock 自己的逻辑, yk 数据部分的修改逻辑也只会关心自己(通常这都是 2 个页面的数据了). 所以这里的一个 "大 switch" 是可以切分的.
{
clock: {
count: 0
},
yk: {
age: 0
}
...
}
function reducer(state, action) {
switch (action.type) {
case 'clock_add':...
case 'clock_cnum'...
case 'yk_older': ...
case 'yk_forever18': ...
default: {
return state
}
}
}
combineReducers 就是对小的 reducer 进行合并的:
function clockReducer(state, action) {
switch (action.type) {
case 'clock_addOne': ...
case 'clock_cnum': ...
default: {
return state
}
}
}
function ykReducer(state, action) {
switch (action.type) {
case 'yk_older': ...
case 'yk_forever18': ...
default: {
return state
}
}
}
function reducer(state, action) {
return {
clock: clockReducer(state, action),
yk: ykReducer(state, action),
}
}
题外话: 这里的 combineReducers 如果小 reducer 特别多, 会有一些性能问题: 因为对于每一个 action,都是走了所有的 reducer. 如果我们场景特殊, 是我们刚才说的 一块数据的逻辑 只对于一个 reducer, 可以使用下面的变种 (只会执行一个 reducer, 需要保证 action 前缀和 store 中 key 一致):
function combineReducers(reducers) {
return function (state, action) {
const keys = Object.keys(reducers)
const newState = {}
keys.forEach(key => {
newState[key] = reducers[key](state[key], action)
})
return newState
}
}
这里有一个完整的保护 middleware, bindActionCreators, combineReducers 所有特性的 完整的例子
function combineReducersVariant(reducers) {
return function (state, action) {
const lineIndex = action.type.indexOf("_")
const actionKey = action.type.substring(0, lineIndex)
const newS = reducers[actionKey](state[actionKey], action)
return state[actionKey] === newS ? state : {
...state,
[actionKey]: newS
}
}
}
代码托管在 git
安装: npm install tiny-redux --save
来源: https://juejin.im/post/5a5e9dfe6fb9a01c95261b24