redux 和 react-redux 部分源码阅读

305 阅读6分钟

从源代码的入口文件发现,其实 redux 最终就只是导出了一个对象,对象中有几个方法,代码如下:

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}

所以重点分析几个方法:

createStore 方法

方法中定义的一些变量:

let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false

这些变量会被 dispatch 或者别的方法引用,从而形成闭包。这些变量不会被释放。

创建 srore 的方法最终返回的是一个对象。对象中含有比较重要的方法dispatch,subscribe,getState

return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [?observable]: observable
}

其中 createStore 的第三个参数是应用中间件来做一些增强操作的。

if (typeof enhancer !== 'undefined') { // 如果增强方法存在就对 createStore 进行增强
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
}

subscribe 方法

其中 subscribe 用来注册监听方法,每一次注册后会将监听方法维护到数组currentListeners中,currentListenerscreateStore 中的一个变量,由于被 subscribe 引用着所以形成了一个闭包。也就是通过闭包来维护状态。

let currentListeners = []

dispatch 方法

dispatch 方法用来分发 action, 函数里面会生成新的 currentState, 会执行所有注册了的函数。

核心代码:

try {
  isDispatching = true
  currentState = currentReducer(currentState, action) // 生成新的 state
} finally {
  isDispatching = false
}

const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
  const listener = listeners[i]
  listener()
} // 遍历执行注册函数

getState

仅仅用来获得当前的 state:

function getState() {
    return currentState
}

combineReducers 函数

函数中定义的一些变量,

const finalReducers = {}
const finalReducerKeys = Object.keys(finalReducers)

这个函数最后返回的是一个函数 combination, 返回的函数中引用了 finalReducersfinalReducerKeys,形成了闭包。

出于业务场景考虑,不同的模块采用不同的 reducer 进行处理,所以 reducer 函数有很多。这些 reducer 会遍历执行。

每一次 dispatch 一个 action 的时候就会执行

currentState = currentReducer(currentState, action) // 生成新的 state

这里的 currentReducer 就是返回的 combination 函数。combination 函数中的核心代码:

function combination(state = {}, action) {
    ...
    let hasChanged = false
    // 每一次 reducer 执行的时候都会生成一个新的对象来作为新的 state 
    const nextState = {}
    // 通过 for 循环遍历 reducer 
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      
      // 获取当前的 state
      const previousStateForKey = state[key]
      
      // 执行相应的 reducer 后会生成新的 state
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      
      // 给新的 state 赋值
      nextState[key] = nextStateForKey
      
      // 如果是一个简单类型比如 string,number 
      // 如果前后值一样就不会触发改变
      // 但如果 state 中某个值是一个对象,
      // 尽管前后对象中的值一样,但是引用地址变化,还是会触发改变
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    
    // 所以如果简单值没有变化并且没有对象的引用地址改变就会返回原来的 state
    return hasChanged ? nextState : state
}

结合 react-redux 中向 redux 订阅的方法发现

subscribe() {
    const { store } = this.props  // 这里的 store 是 createStore 方法执行后返回的对象
     
    this.unsubscribe = store.subscribe(() => { // 通过订阅方法注册监听事件
      const newStoreState = store.getState() // 获取新的 state
    
      if (!this._isMounted) {
        return
      }
    
      // 通过使用函数替代对象传入 setState 的方式能够得到组件的 state 和 props 属性可靠的值。
      this.setState(providerState => {
        // 如果值是一样的就不会触发更新
        if (providerState.storeState === newStoreState) {
          return null
        }
    
        return { storeState: newStoreState }
      })
    })
    
    // Actions might have been dispatched between render and mount - handle those
    const postMountStoreState = store.getState()
    if (postMountStoreState !== this.state.storeState) {
      this.setState({ storeState: postMountStoreState })
    }
}

在注册的 listen 方法中会发现如果最 新的state和原来的state一样 就不会触发 setState 方法的执行,从而就不会触发 render

applyMiddleware 使用中间件

源码:

export default function applyMiddleware(...middlewares) {
    return createStore => (...args) => { // 接收 createStore 函数作为参数
        const store = createStore(...args)
        let dispatch = () => {
          throw new Error(
            `Dispatching while constructing your middleware is not allowed. ` +
              `Other middleware would not be applied to this dispatch.`
          )
        }
        
        const middlewareAPI = {
          getState: store.getState,
          dispatch: (...args) => dispatch(...args)
        } // 中间件函数接收的 API 参数,能够获取到当前的 state 和 createStore 函数的参数
        // 所以这里就向中间件函数中传递了参数
        const chain = middlewares.map(middleware => middleware(middlewareAPI))
        // 通过函数组合生成一个新的 dispatch 函数
        dispatch = compose(...chain)(store.dispatch)
        
        return {
          ...store,
          dispatch
        } // 这里返回的是最后生成的 store,相比不使用中间件的区别是对 dispatch 进行了增强。
    }
}

结合 createStore 中的源码:

return enhancer(createStore)(reducer, preloadedState)

所以上面 applyMiddleware 中返回的函数就是这里的 enhancer 方法,接收 createStore 作为参数。

(reducer, preloadedState) 对应着中间件中的 (...args)

react-redux

react-redux 通过提供 Provider 组件将 store 和整个应用中的组件联系起来。确保整个组件都可以获得 store, 这是通过 Context 来实现的。

Provider 组件最终渲染的组件:

render() {
    const Context = this.props.context || ReactReduxContext

    return (
      <Context.Provider value={this.state}>
        {this.props.children}
      </Context.Provider>
    )
}

其中 state 的定义如下:

const { store } = props
this.state = {
  storeState: store.getState(),
  store
}

所以 Provider 给应用提供 store 的写法如下,属性名必须是 store

<Provider store={store}>
  <Router />
</Provider>

redux-thunk

redux-thunk 是一个中间件,直接看中间件的源代码是绝对不可能看明白的

中间件不是一个完整的个体。它是为了丰富或者扩展某个模块而出现的,其中会调用一些原来的模块的方法,所以如果不看源模块的对应的方法实现,根本无法理解。

所以要想看懂一个中间件,必须结合源模块的代码一起看。

Thunk 函数的含义和用法

JavaScript 语言是传值调用,它的 Thunk 函数含义有所不同。在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数

如何让 dispatch 分发一个函数,也就是 action creator??

dispatch 的参数只能是一个普通的对象,如果要让参数是一个函数,需要使用中间件 redux-thunk

设计思想就是一种面向切面编程AOP,对函数行为的增强,也是装饰模式的使用

redux-thunk 源码:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

如果只是用了 thunk,那么最终增强版的 dispatch 就是

action => {
    // 当 dispatch 参数是一个函数的时候执行这里
    if (typeof action === 'function') { 
      // 这里的 dispatch 就是最原始的 dispatch
      // 所以 action 函数中可以直接使用参数 dispatch 和 getState 函数
      return action(dispatch, getState, extraArgument);
    }

    return next(action); // 这里的 next 是 store.dispatch
}

异步操作带代码

异步操作如果使用 action creator, 则至少要送出两个 Action:

  • 用户触发第一个 Action,这个跟同步操作一样,没有问题;
  • action creator 函数中送出第二个 Action

代码实例:

handleClick = () => {
    const { dispatch } = this.props
    dispatch(this.action); // 发出第一个 action(函数)
}

action = (dispatch, getState) => setTimeout(() => {
    dispatch({ type: 'REQUESTSTART' })
}, 1000) // 发出第二个 action(普通对象)

思考

异步代码的处理一定要使用 redux-thunk吗?

非也。在触发含有异步代码的函数执行时,把 dispatch 函数作为一个参数传给函数,然后这个异步函数里面在合适的时机调用 dispatch 发出 action 就行。

上面的异步代码可改写如下:

handleClick = () => {
    const { dispatch } = this.props
    this.action(dispatch);
}

action = dispatch => setTimeout(() => {
    dispatch({ type: 'REQUESTSTART' })
}, 1000)

不过相比 redux-thunk 有个缺陷就是不能获取 getState 这个方法。

使用示例

使用 redux 演示代码