redux 的那些事 (一)

前言: createStore 是redux 创建 store 的方法,通过传入 reducer(数据处理函数),initialState(预设数据),enhancer(中间件,如时间穿梭等)返回一个store实例,具有getState,subscribe,dispatch,replaceReducer。接下将逐一分析各函数作用

给出一个最小化example

import { createStore } from 'redux'

function counterReducer (state = { value: 0 }, action) {
  switch (action.type) {
    case 'counter/incremented':
      return { value: state.value + 1 }
    case 'counter/decremented':
      return { value: state.value - 1 }
    default:
      return state
  }
}

let store = createStore(counterReducer)

store.subscribe(() => console.log(store.getState()))

store.dispatch({ type: 'counter/incremented' })
// {value: 1}
store.dispatch({ type: 'counter/incremented' })
// {value: 2}
store.dispatch({ type: 'counter/decremented' })
// {value: 1}
复制代码

createStore

let store = createStore(counterReducer)
复制代码

从实例可以看出,createStore中真正需要传递的仅仅是reducer这个处理函数,initialState enhance都是可选。

export default function createStore(reducer) {
    // xxxx
    
    const store = {
        dispatch: dispatch
        subscribe,
        getState,
        replaceReducer,
        [$$observable]: observable
    }
  	return store
}
复制代码

并且返回的store 中 是一个具有 dispatch subscribe getState replaceReducer 的对象

并且值得注意的是,createStore最后会调用dispatch({ type: ActionTypes.INIT } as A)

来获取初始的state值。

dispatch

store.dispatch({ type: 'counter/incremented' })
复制代码

dispatchredux 中唯一合法修改state的方法,通过调用dispatch 并且传入action来调用, 通常来说action是一个具有type payload两个属性的对象

if (!isPlainObject(action)) {
      throw new Error(
        `Actions must be plain objects. Instead, the actual type was: '${kindOf(
          action
        )}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
    )
}
 if (typeof action.type === 'undefined') {
     throw new Error(
         'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
     )
 }

if (isDispatching) {
    throw new Error('Reducers may not dispatch actions.')
}
复制代码

dispatch中 首先会做一些错误判断,

  • !isPlainObject(action) 判断action是否是是一个原生的对象字面量 或者是一个new Object() 对象
  • action对象中必须有type 属性
  • 是否处于dispatch 处理过程中
try {
    isDispatching = true
    currentState = currentReducer(currentState, action)
} finally {
    isDispatching = false
}
复制代码
  • 判断完后就是执行Reducer。更新最新的state
 const listeners = (currentListeners = nextListeners)
 for (let i = 0; i < listeners.length; i++) {
     const listener = listeners[i]
     listener()
 }
复制代码

再逐一通知所有订阅对象更新数据

return action
复制代码

最后是返回action 对象

  • 值得注意的是 dispatch 过程中是 依次调用dispatch 通过 isDispatching 来控制

subscribe

subscribe 是在组件内订阅store的更新,每当有dispatch 更新state数据后 就会触发。

可以在subscribe 中获取最新的state数据。并且subscribe 会返回一个 unsubscribe函数

在不再需要监听是的时候,调用unsubscribe 可以 清除监听函数

if (typeof listener !== 'function') {
    throw new Error(
        `Expected the listener to be a function. Instead, received: '${kindOf(
            listener
        )}'`
    )
}

if (isDispatching) {
    throw new Error(
        'You may not call store.subscribe() while the reducer is executing. ' +
        'If you would like to be notified after the store has been updated, subscribe from a ' +
        'component and invoke store.getState() in the callback to access the latest state. ' +
        'See https://redux.js.org/api/store#subscribelistener for more details.'
    )
}
复制代码
  • 同样 subscribe 开始也是会判断
    • subscribe 传入的必须是一个监听函数
    • dispatching 中不能添加监听对象
let isSubscribed = true

ensureCanMutateNextListeners()
nextListeners.push(listener)
复制代码
  • ensureCanMutateNextListeners 是对listener list 的浅拷贝

    store.subscribe(() => {
        console.log('outer');
        store.subscribe(() => {
            console.log('inner');
        })
    })
    复制代码

    这里在subscribe 中又嵌套了subscribe。其中嵌套的subscribe 只会在第二次 store 更新后,才会调用。

    因为 新添的listener 是加入到nextListeners 中,不是加入到 currentListeners

  • 有新添的listener 对象都是加入到nextListeners 中。

return function unsubscribe() {
    if (!isSubscribed) {
        return
    }

    if (isDispatching) {
        throw new Error(
            'You may not unsubscribe from a store listener while the reducer is executing. ' +
            'See https://redux.js.org/api/store#subscribelistener for more details.'
        )
    }

    isSubscribed = false

    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
    currentListeners = null
}
复制代码

subscribe会返回一个unsubscribe 函数,用这个函数来终止订阅。

getState

getState是获取store中数据的方法,原理也比较简单,就是利用闭包来保存state,需要的时候直接调用即可取走

  function getState(){
    if (isDispatching) {
      throw new Error(
        'You may not call store.getState() while the reducer is executing. ' +
          'The reducer has already received the state as an argument. ' +
          'Pass it down from the top reducer instead of reading it from the store.'
      )
    }

    return currentState
  }

复制代码
  • 同样在dispatching过程中不能获取state,为了防止dispatch过程中state修改

replaceReducer

这个也比较好理解,就是可以中途更换reducer 函数

function counterReducer (state = { value: 0 }, action) {
  switch (action.type) {
    case 'counter/incremented':
      return { value: state.value + 1 }
    case 'counter/decremented':
      return { value: state.value - 1 }
    default:
      return state
  }
}
function counterReducer1 (state = { value: 0 }, action) {
  switch (action.type) {
    case 'counter/incremented':
      return { value: state.value + 10 }
    case 'counter/decremented':
      return { value: state.value - 10 }
    default:
      return state
  }
}
const store = createStore(counterReducer)

store.replaceReducer(counterReducer1)
复制代码

这样store reducer 即更换为 counterReducer1

小结

createStore 通过传入的 reducer 来创建一个具有

  • dispatch: 唯一合法修改state数据的方法
  • getState: 获取state数据方法
  • subscribe: 订阅state的改变
  • replaceReducer: 替换当前正在使用的reducer

相对于redux 中间件与action 来说,这一小节在redux 日常的使用中占到非常大的比重。

也是希望自己能了解当中究竟是怎么工作,面对问题时知道解决方案

猜你喜欢

转载自juejin.im/post/7086839343788064776