Redux源码分析

813 阅读4分钟
import './index.css'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App/App'
import { createStore } from 'redux'

const store = createStore((state = { count: 1 }, action) => {
    switch (action.type) {
        case 'ADD':
            state.count += action.value
            break
        case 'MINUS':
            state.count -= action.value
            break
    }

    return state
})

function createAction(type, value) {
    return () => {
        store.dispatch({
            type,
            value
        })
    }
}

function render() {
    ReactDOM.render(
        <App {...store.getState()} increase={createAction('ADD', 1)} decrease={createAction('MINUS', 1)} />,
        document.getElementById('root')
    )
}

render()
store.subscribe(render)

以上是使用Redux的一个最简单的例子。涵盖了redux的基本使用方式,用户点击按钮 --> dispatch(action) --> 更新store --> 触发监听函数render,重绘。

查看案例

文件目录

  • applyMiddleware.js // Redux的插件机制实现,可以重点学习一下
  • bindActionCreators.js // 将ActionCreator和dispatch绑定的工具
  • combineReducers.js // 将store分层的工具
  • compose.js // 将数个函数合并嵌套执行
  • createStore.js // 核心,生成store
  • index.js // 入口

combineReducers

const store = createStore(combineReducers({PageA: PageAReducer, PageB: PageBReducer}))

因为createStore的接收的是一个函数,当dispatch发生时,它就会被执行,将当前的store和被触发的action作为参数,传入这个函数。所以combineReducers的主要任务就是:遍历一个combinedReducers对象,将每个子reducer依次执行,并将执行结果赋予其所对应的key,最后返回一个总的newStore。有了以上的基础,我们来看源码实现:

export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    // 将每一个子reducer,保存到finalReducer对象中
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  return function combination(state = {}, action) {
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      // 取出一个子reducer
      const reducer = finalReducers[key]
      // 取得这个key对应的旧的值
      const previousStateForKey = state[key]
      // 核心操作,将旧的state和action传入这个reducer,得到新的state
      const nextStateForKey = reducer(previousStateForKey, action)
      
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

applyMiddleware

createStore(reducers, applyMiddleware(reduxLogger, reduxPromise, reduxThunk))

这个插件机制是Redux一个很重要的功能,使得我们可以很方便的对action做各种各样的操作。比如日志打印,异步数据获取等等,只要添加一个middleware,就可以实现。这个类似于Express或者Koa中的中间件机制。我们来看一下他们是如何实现的

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    // 这里通过将createStore方法和参数传入的方式,得到store
    // 为什么不直接将store传入呢?因为,我们查看createStore的代码,可以发现
    // return enhancer(createStore)(reducer, preloadedState)
    // 这里的 enhancer其实就是当前的applyMiddeware,通过这种方式,可以保证window.store是由applyMiddleware最后return出去的
    // 这个对我们以后写组件有借鉴意义,比如
    // 我们有一个 ComponentA:  const compA = new Component(args);
    // 现在我们想强化一下CompoentA的功能,通过传入组件B的方式,那么, const enhancedCompA = new ComponentA(args, B)
    // 这时候,如果B需要ComponentA原有的结果,那么就可以使用这种方式,最后返回的结果一定是经过B处理过的
    const store = createStore(...args)
    
    let dispatch = () => {}
    
    let chain = []

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    
    // 将getState和dispatch,作为参数传给每一个middleware
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    
    /** 
    这个compose的作用就是: compose(a,b,c) ===> (...args) => a(b(c(...args)))
    
    假设有[a,b,c]三个middleware,他们都长这样:
    
    ({ dispatch, getState }) => next => action => {
        // 对action的操作 blablabla
        
        return next(action);
    };
    
    那么,c会最先接收到一个参数,就是store.dispatch,作为它的next。然后c使用闭包将这个next存起来,把自己作为下一个middleware b的next参数传入。这样,就将所有的middleware串起来了。
    最后,如果用户dispatch一个action,那么执行顺序会是: c --> b --> a
    **/
    
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

createStore

createStore主要会返回三个东西:

  • getState
  • subscribe
  • dispatch

getState

很简单,返回当前的store

 function getState() {
    // 这里有个锁的机制,保证当另一个action在dispatch时,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
  }

subscribe

注册监听函数

function subscribe(listener) {
    if (isDispatching) {
      throw new Error(...)
    }

    let isSubscribed = true

    // 这里Redux维护了两个Listener List:
    // currentListeners
    // nextListeners
    // 因为currentListeners和nextListeners是独立的,就可以保证当currentListeners还在执行的时候,调用subscribe或unsubscribe是不会影响到原先原先注册的listeners的。
    // 只有当下一次dispatch前,nextListeners又被同步给了currentListeners,之前的注册注销才会生效
     
    ensureCanMutateNextListeners()
    nextListeners.push(listener)

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

      if (isDispatching) {
        throw new Error(...)
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

dispatch

redux中最常用的一个方法了

function dispatch(action) {
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.');
    }

    try {
      isDispatching = true;
      // 使用绑定的reducer,计算出新的store
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }

    // 触发所有绑定的监听器
    var listeners = currentListeners = nextListeners;
    for (var i = 0; i < listeners.length; i++) {
      var listener = listeners[i];
      listener();
    }

    return action;
  }