手写redux核心原理

本文从零实现一个简单的 redux ,主要内容在于redux 的设计思路及实现原理

redux 是一个状态管理器,里面存放着数据,比如我们创建 store.js,在里面我们存放着这些数据,只需要在任何地方引用这个文件就可以拿到对应的状态值:

let state = {
  count: 1
}

console.log(state.count)
state.count = 2
复制代码

复制代码现在我们实现了状态(计数)的修改和使用了!当然上面的有一个很明显的问题:这个状态管理器只能管理 count,修改 count 之后,使用 count 的地方不能收到通知。

实现 subscribe

我们可以使用发布-订阅模式来解决这个问题。我们用个函数封装一下 redux

function createStore(initState) {
  let state = initState
  let listeners = []

  /* 订阅函数 */
  function subscribe(listener) {
    listeners.push(listener)
  }

  function changeState(newState) {
    state = newState
    /* 执行通知 */
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
  }

  function getState() {
    return state
  }

  return { subscribe, changeState, getState }
}
复制代码

这里我们完成了一个简单的状态管理器。state 的数据可以自由的定, 我们修改状态,在订阅的地方监听变化,可以实现监听。

let initState = {
  count: 1,
  detail: {
    age: 24
  }
}

let store = createStore(initState)

store.subscribe(() => {
  let state = store.getState()
  console.log('t1: ', state)
})

store.changeState({ ...store.getState(), count: store.getState().count + 1 })

// t1:  { count: 2, detail: { age: 24 } }
复制代码

这里需要理解的是 createStore,提供了 changeStategetStatesubscribe 三个能力。

在上面的函数中,我们调用 store.changeState 可以改变 state 的值,这样就存在很大的弊端了。比如 store.changeState({})

我们一不小心就会把 store 的数据清空,或者误修改了其他组件的数据,那显然不太安全,出错了也很难排查,因此
我们需要有条件地操作 store,防止使用者直接修改 store 的数据。
复制代码

因此,我们需要一个约束来修改 state 的值,而不允许意外的情况来将 state 的值清空或者误操作。分两步来解决这个问题:

  • 1. dispatch: 制定一个 state 修改计划,告诉 store,我的修改计划是什么。

  • 2. reducer: 修改 store.changeState 方法,告诉它修改 state 的时候,按照我们的计划修改。

我们将 store.changeState 改写为 store.dispatch, 在函数中传递多一个 reducer 函数来约束状态值的修改。

实现 reducer

reducer 是一个纯函数,接受一个 state, 返回新的 state

function createStore(reducer, initState) {
  let state = initState
  let listeners = []

  /* 订阅函数 */
  function subscribe(listener) {
    listeners.push(listener)
  }

  /* state 值的修改 */
  function dispatch(action) {
    state = reducer(state, action)
    /* 执行通知 */
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
  }

  function getState() {
    return state
  }

  return { subscribe, dispatch, getState }
}
复制代码

我们来尝试使用 dispatchreducer 来实现自增和自减

let initState = {
  count: 1,
  detail: {
    age: 24
  }
}

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 }
    case 'DECREMENT':
      return { ...state, count: state.count - 1 }
    default:
      return state
  }
}

let store = createStore(reducer, initState)

store.subscribe(() => {
  let state = store.getState()
  console.log('t1: ', state)
})

store.dispatch({ type: 'INCREMENT' }) // 自增
store.dispatch({ type: 'DECREMENT' }) // 自减
store.dispatch({ count: 2 }) // 计划外:不生效
复制代码

我们知道 reducer 是一个约束函数,接收老的 state,按计划返回新的 state。那我们项目中,有大量的 state,每个 state 都需要约束函数,如果所有的计划写在一个 reducer 函数里面,会导致 reducer 函数及其庞大复杂。所以我们需要将封装 combineReducers 来优化 reducer 函数。

实现 combineReducers

粒子化 reducer

  • 传入对象参数,key 值即为 state 状态树的 key 值, value 为对应的 reducer 函数。

  • 遍历对象参数,执行每一个 reducer 函数,传入 state[key], 函数获得每个 reducer 最新的 state 值。

  • 耦合 state 的值, 并返回。返回合并后的新的 reducer 函数。

function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)

  /*返回合并后的新的reducer函数*/
  return function combination(state = {}, action) {
    /*生成的新的state*/
    const nextState = {}

    /*遍历执行所有的reducers,整合成为一个新的state*/
    for (let i = 0; i < reducerKeys.length; i++) {
      const key = reducerKeys[i]
      const reducer = reducers[key]
      /*之前的 key 的 state*/
      const previousStateForKey = state[key]
      /*执行 分 reducer,获得新的state*/
      const nextStateForKey = reducer(previousStateForKey, action)

      nextState[key] = nextStateForKey
    }
    return nextState
  }
}

//使用 combineReducers:
let state = {
  counter: { count: 0 },
  detail: { age: 24 }
}

function counterReducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 }
    default:
      return state
  }
}

function detailReducer(state, action) {
  switch (action.type) {
    case 'INCREMENT-AGE':
      return { age: state.age + 1 }
    default:
      return state
  }
}

const reducers = combineReducers({
  counter: counterReducer,
  info: detailReducer
})

let store = createStore(reducers, initState)

store.subscribe(() => {
  let state = store.getState()
  console.log('t1: ', state)
})

store.dispatch({ type: 'INCREMENT' })
store.dispatch({ type: 'INCREMENT-AGE' })
复制代码

我们把 reducer 按组件维度拆分了,通过 combineReducers 合并了起来。 但是还有个问题, state 我们还是写在一起的,这样会造成 state 树很庞大,不直观,很难维护。我们需要拆分,一个 state,一个 reducer 写一块。

粒子化 state

改写 combineReducers 函数,在 createStore 函数中执行 dispatch({ type: Symbol() })

function createStore(reducer, initState) {
  let state = initState
  let listeners = []

  /* 订阅函数 */
  function subscribe(listener) {
    listeners.push(listener)
  }

  function dispatch(action) {
    state = reducer(state, action)
    /* 执行通知 */
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
  }

  /* 注意!!!只修改了这里,用一个不匹配任何计划的 type,来获取初始值 */
  dispatch({ type: Symbol() })

  function getState() {
    return state
  }

  return { subscribe, dispatch, getState }
}

//将 state 分别传入各自的 reducer:
function counterReducer(state = { count: 1 }, action) {
  //...
}

function detailReducer(state = { age: 24 }, action) {
  //...
}

// 合并 reducer
const reducers = combineReducers({
  counter: counterReducer,
  info: infoReducer
})

// 移除 initState
let store = createStore(reducers)

console.log(store.getState()) // { counter: { count: 1 }, detail: { age: 24 } }
复制代码
  • createStore 的时候,用一个不匹配任何 typeaction,来触发 state = reducer(state, action)
  • 因为 action.type 不匹配,每个子 reducer 都会进到 default 项,返回自己初始化的 state,这样就获得了初始化的 state 树了。
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享