阅读 88

手写简易版redux

应用程序的整个全局状态存储在单个store对象树中(单一共享数据存储)。改变状态的唯一方法是创建一个action(一个描述发生了什么的对象),并将它dispatch到store,编写纯reducer函数,该函数根据旧状态和动作计算新状态。

state

单一的全局数据源,所有状态都存储在单个树中,那么难以实现的状态管理会突然变得微不足道,该状态对外部只读

action

改变state的唯一方法是发出一个action(一个描述发生了什么的对象);所有的变化都是集中管理的,并且按照严格的顺序一一发生

reducer

纯函数,它接受前一个状态和一个动作,并返回下一个状态;返回的是新的状态对象,而不是改变以前的状态

store

保存应用程序状态树的对象。Redux应用程序中应该只有一个store,因为合并发生在reducer级别。

编写Redux

使用单例模式实现唯一的数据源

const createStore = (() => {
  let store = null
  const state = {}
  const getState = () => { }
  const dispatch = () => { }
  const subscribe = () => { }
  const unSubscribe = () => {}
  const replaceReducer = () => { }
  return () => {
    if (!store) {
      store = {
        getState,
        dispatch,
        subscribe,
        replaceReducer,
      }
    }
    return store
  }
})()

export default createStore
复制代码

通过调用createStore(),可以拿到store,且多次调用生成的是同一个store,store里面包含了

  • dispatch(action) 基本调度函数。
  • getState() 返回store的当前状态。
  • subscribe(listener) 注册一个在状态改变时调用的函数。
  • replaceReducer(nextReducer) 更换store的reducer以计算state、动态假装一些reducer、实现热重载

getState返回一个state的副本防止消费者直接修改其引用属性,使用lodash的cloneDeep方法来返回

const getState = () => {
    return _.cloneDeep(state)
}
复制代码

添加和移除订阅者

const listeners = [];
const subscribe = (fn) => {
    listeners.push(fn)
}
const unsubscribe = (fn) => {
    const index = listeners.indexOf(fn)
    listeners.splice(index, 1)
}
复制代码

通常使用闭包的方式将添加和移除放在一起,移除函数作为添加的返回值

const listeners = [];
const subscribe = (fn) => {
  listeners.push(fn)
  const unsubscribe = () => {
    const index = listeners.indexOf(fn)
    listeners.splice(index, 1)
  }
  return unsubscribe
}
复制代码

初始化reducer并加入修改reducer的函数

let currentReducer = (state, action) => {
  return state;
}
const replaceReducer = (reducer) => {
  currentReducer = reducer
}
复制代码

最后通过实现dispatch来完成订阅者消息的触发,防止修改到源数据,都使用数据的副本传递给消费者

const dispatch = (action) => {
  const preState = _.cloneDeep(state)
  state = currentReducer(_.cloneDeep(state), action)
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener(preState, _.cloneDeep(state))
  }
}
复制代码

完整代码如下:

import _ from 'lodash'
const createStore = (() => {
  let store = null
  let state = {}
  const listeners = []
  let currentReducer = (state, action) => {
    return state;
  }
  const getState = () => {
    return _.cloneDeep(state)
  }
  const dispatch = (action) => {
    const preState = _.cloneDeep(state)
    state = currentReducer(_.cloneDeep(state), action)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener(preState, _.cloneDeep(state))
    }
  }
  const subscribe = (fn) => {
    listeners.push(fn)
    const unsubscribe = () => {
      const index = listeners.indexOf(fn)
      listeners.splice(index, 1)
    }
    return unsubscribe
  }
  const replaceReducer = (reducer) => {
    currentReducer = reducer
  }
  return () => {
    if (!store) {
      store = {
        getState,
        dispatch,
        subscribe,
        replaceReducer,
      }
    }
    return store
  }
})()

export default createStore

复制代码

使用示例:

const store = createStore()
store.subscribe((prev, v) => console.log(prev, v, '第 1 个消费者'))
store.subscribe((prev, v) => console.log(prev, v, '第 2 个消费者'))
store.replaceReducer((state, action) => {
  const { type, ...rest } = action
  if (action.type === 'test') {
    return {
      ...state,
      ...rest,
    }
  } else {
    return state
  }
})
store.dispatch({ type: 'test', value1: 'testValue1' })
store.dispatch({ type: 'test', value2: 'testValue2' })
store.dispatch({ type: 'other', value: 'other' })
复制代码

有两个订阅者,在3次dispatch后都执行了reducer得到新的state结果 image.png

文章分类
前端
文章标签