Life of xhu

About

Redux源码笔记(1)

Jan 16, 2016

  |   #Redux

众所周知,React虽然在前端大放异彩,但是根本上来说,它只是一个View层的框架,一般初学者都会使用Facebook官方的Flux作为数据管理框架,然而在2015年,Redux作为另一个更专业的数据框架异军突起,甚至在GitHub上的star超过了Flux,我们先看官网的介绍:

Redux is a predictable state container for JavaScript apps.

从简介上可以看出,Redux已经不满足于简单做一个数据框架了,而是希望成为一个状态容器,在React中使用Redux,本质上只是用React作为View来展现Redux中的状态罢了。而且Redux的代码量并不大,并且是用ES6的语法编写的,还有着充分的注释,阅读起来非常舒服,大致浏览一遍后,我决定通读这份代码,现在我使用Redux也算是得心应手了,就当是在“知其然”的同时,也努力做到“知其所以然”吧。

src/index.js

import createStore from './createStore'
import combineReducers from './utils/combineReducers'
..

export {
  createStore,
  combineReducers,
  ...
}

这个是整个项目的基础文件,从这个文件我们可以看出,Redux一开始就没有向传统前端妥协,而是专为模块化准备的,整个项目的功能都需要通过require/import来使用,没有导出任何全局变量来供前端通过script加载来使用。

src/createStore.js

import isPlainObject from './utils/isPlainObject'

export var ActionTypes = {
  INIT: '@@redux/INIT'
}

import了一个isPlainObject方法,用来检测一个变量是否为简单对象,并且定义了一个保留的ActionType,当使用这个保留的ActionType时,将会返回初始的state。


export default function createStore(reducer, initialState) {
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  var currentReducer = reducer
  var currentState = initialState
  var listeners = []
  var isDispatching = false

这个文件默认export一个名为createStore的函数,这个函数接受reducer和初始state为参数,reducer的类型必须是函数,否则会抛出异常。

然后定义了一系列变量,用来保存当前的reducer和state,一个listener数组用于观察者模式,并且初始store不处在dispatch action的状态。


  function getState() {
    return currentState
  }

获得当前state。


  function subscribe(listener) {
    listeners.push(listener)
    var isSubscribed = true

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

      isSubscribed = false
      var index = listeners.indexOf(listener)
      listeners.splice(index, 1)
    }
  }

这个函数就是实现观察者模式,通过subscribe函数给state添加一个listener,这个listener会被加入到listeners数组中。

添加listener的代码没有什么好说的,真正有趣的是在后面移除listener的函数,这里就是函数thunk化的思想,我设想了一下,如果由我来编写这个unsubscribe函数,我可能会把这个函数像subscribe一样定义,然而在这段代码中,这个函数被作为了subscribe函数的返回值,也就是说,当我们初始化一个store的时候,这个store是没有unsubscribe函数的,只有当我们给一个store加上listener的时候,unsubscribe函数便会自动创建,并且不用传入listener,因为unsubscribe函数形成了一个闭包,listener已经在初始化unsubscribe函数的时候成为了它的内部变量。

示例代码:

// original, written by me
store.subscribe(handler);
store.unsubscribe(handler);

// current, in Redux
var listener = store.subscribe(handler);
listener.unsubscribe();

显然后者更优雅一点。


  function dispatch(action) {
    if (!isPlainObject(action)) {
      ...
    }

    if (typeof action.type === 'undefined') {
      ...
    }

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

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    listeners.slice().forEach(listener => listener())
    return action
  }

这里实现了store里的dispatch函数,这个函数接受一个action对象,并且根据action里的内容,分配到相应的reducer来对state进行更改,一个action能否生效有三个前提:

  1. action是一个PlainObject,即朴素对象,简单的说就是通过对象字面量创建的对象;
  2. action对象必须包含名为type的key;
  3. store不能正在出于dispatch的状态,也就是同一个时刻只能有一个action在改变store内容,确保store稳定。

然后,把isDispatching置为true,也就是说当前store进入dispatch状态,并且把当前state和action传入当前reducer做处理。

处理完成后,把listensers数组复制一遍,至于为什么要复制而不是直接使用listeners数组,我猜可能是为了在执行前后保证listeners数组内容不变吧,然后遍历数组把函数都执行一边。

最后,返回action对象。


  function replaceReducer(nextReducer) {
    currentReducer = nextReducer
    dispatch({ type: ActionTypes.INIT })
  }

这个方法用来替换当前的reducer,当需要动态变更reducer的时候可以使用这个方法,当然一般是不用的,然后对state进行初始化


  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer
  }
}

初始化state,并且暴露几个关键方法。到这里,createStore.js这个文件就结束了。