深入redux源码 (1)

redux 的安装

npm install redux --save

redux 有五个js构成,纯函数实现


因为 redux 是纯函数,所以很方便进行测试.在webStorm新建个node环境,安装redux,就可以跑redux的例子.

先看个最简单的使用例子:

/**
 * Created by lijie on 16/8/7.
 */
// 首先定义一个改变数据的plain函数,成为reducer
'use strict';
function count (state, action) {
    var defaultState = {
        year: 2015,
    };
    state = state || defaultState;
    switch (action.type) {
        case 'add':
            return {
                year: state.year + 1
            };
        case 'sub':
            return {
                year: state.year - 1
            }
        default :
            return state;
    }
}

// store的创建
var createStore = require('redux').createStore;
var store = createStore(count);

// store里面的数据发生改变时,触发的回调函数
store.subscribe(function () {
    console.log('the year is: ', store.getState().year);
});

// action: 触发state改变的唯一方法(按照redux的设计思路)
var action1 = { type: 'add' };
var action2 = { type: 'add' };
var action3 = { type: 'sub' };

// 改变store里面的方法
store.dispatch(action1); // 'the year is: 2016
store.dispatch(action2); // 'the year is: 2017
store.dispatch(action3); // 'the year is: 2016

运行结果如下:


该例子只用到了一个API: createStore, 先看这个主干 createStore.js

createStore.js

参数

function createStore(reducer, initialState, enhancer) {
    ...
}

reducer 示例中的counter函数,作用是根据action处理state的变化

initialState 初始化的 state 状态树

enhancer 增强函数,需要用applyMiddleware 函数加入自定义的功能.
例如 常用第三方库redux-thunkredux-logger

subscribe 函数

  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.');
    }

    var isSubscribed = true;

    ensureCanMutateNextListeners();
    nextListeners.push(listener);

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

      isSubscribed = false;

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

listener 表示观察者函数
nextListeners: 最新的listener 数组

该函数在做的事情: 将之前的listener数组复制给nextListener,并将当前的注册的listener也加入nextListener中

返回的是 清除当前观察者对象的函数 ,该函数就将当前listener从数组中又拿掉了.以后再通过dispatch发送action时,该listener将不会通知该函数了.

dispatch 函数

  function dispatch(action) {

    if (!(0, _isPlainObject2["default"])(action)) {
      throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
    }

    if (typeof action.type === 'undefined') {
      throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?');
    }

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

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

    var listeners = currentListeners = nextListeners;
    for (var i = 0; i < listeners.length; i++) {
      listeners[i]();
    }

    return action;
  }

前面一长串方法都是判断发送的Action是否是 对象,actionType是否是 undefined ,是否已经发送过

最关键部分 currentState = currentReducer(currentState, action);

currentReducer 就是初始化赋值给的reducer函数
在dispatch 函数中给到各个reducer进行处理

最后一步 for 循环通知当前所有的观察者

这样的话,示例中所运行的效果就能解释通了.

redux 中间件

redux 库能衍生出丰富的工具集和可拓展的生态系统

为刚才的示例添加一个 监控数据变化的 logger 的中间件

'use strict';

const logger = store => next => action => {
    console.log("pre---"+JSON.stringify(store.getState()));
    const result = next(action);
    console.log("end---"+JSON.stringify(store.getState()));
    return result;
};

function count (state, action) {
    var defaultState = {
        year: 2015,
    };
    state = state || defaultState;
    switch (action.type) {
        case 'add':
            return {
                year: state.year + 1
            };
        case 'sub':
            return {
                year: state.year - 1
            }
        default :
            return state;
    }
}

// store的创建
var createStore = require('redux').createStore;
var applyMiddleware=require('redux').applyMiddleware;

const createStoreWithMiddleware = applyMiddleware(logger)(createStore);
var store=createStoreWithMiddleware(count,undefined);

// store里面的数据发生改变时,触发的回调函数
store.subscribe(function () {
    console.log('the year is: ', store.getState().year);
});
// action: 触发state改变的唯一方法(按照redux的设计思路)
var action1 = { type: 'add' };

// // 改变store里面的方法
store.dispatch(action1); // 'the year is: 2016

运行结果如下:


实现该功能 主要用到了applyMiddleware.js的API

applyMiddleware.js

function applyMiddleware() {
  //在这里 找到所有的 第三方中间件 函数
  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  return function (createStore) {
    return function (reducer, initialState, enhancer) {
      //创建store
      var store = createStore(reducer, initialState, enhancer);
      var _dispatch = store.dispatch; //拿到store的dispatch函数
      var chain = [];

      var middlewareAPI = {
        getState: store.getState,
        dispatch: function dispatch(action) {
          return _dispatch(action);
        }
      };
      //将middlewareAPI 作为第三方中间件 参数,并返回 该所有第三方中间件的函数数组
      chain = middlewares.map(function (middleware) {
        return middleware(middlewareAPI);
      });
      //从右到左, middleware1( middleware2( middleware3(dispatch) ) )
      _dispatch = _compose2["default"].apply(undefined, chain)(store.dispatch);

      return _extends({}, store, {
        dispatch: _dispatch
      });
    };
  };
}

该段代码的作用: 创建store,将中间件转成数组,将store的state与dispatch给到中间件,并嵌套执行.

在这里面有个compose.js的api使用.官方解释作用是 (...args) => f(g(h(...args))).将数组依次嵌套执行.

compose.js

为了更清楚理解compose.js的作用,我们再添加一个 第三方中间件.

'use strict';

const logger = store => next => action => {
    console.log("pre---"+JSON.stringify(store.getState()));
    const result = next(action);
    console.log("end---"+JSON.stringify(store.getState()));
    return result;
};
/**
 * 用 { meta: { delay: N } } 来让 action 延迟 N 毫秒。
 * 在这个案例中,让 `dispatch` 返回一个取消 timeout 的函数。
 */
const timeoutScheduler = store => next => action => {
    if (!action.meta || !action.meta.delay) {
        return next(action)
    }

    let timeoutId = setTimeout(
        () => next(action),
        action.meta.delay
    )

    return function cancel() {
        clearTimeout(timeoutId)
    }
}


function count (state, action) {
    var defaultState = {
        year: 2015,
    };
    state = state || defaultState;
    switch (action.type) {
        case 'add':
            return {
                year: state.year + 1
            };
        case 'sub':
            return {
                year: state.year - 1
            }
        default :
            return state;
    }
}

// store的创建
var createStore = require('redux').createStore;
var applyMiddleware=require('redux').applyMiddleware;

const createStoreWithMiddleware = applyMiddleware(logger,timeoutScheduler)(createStore);
var store=createStoreWithMiddleware(count,undefined);

// store里面的数据发生改变时,触发的回调函数
store.subscribe(function () {
    console.log('the year is: ', store.getState().year);
});
// action: 触发state改变的唯一方法(按照redux的设计思路)
var action1 = { type: 'add', meta: { delay: 2000 }};

// // 改变store里面的方法
store.dispatch(action1); // 'the year is: 2016

添加的中间件 作用是发送带timeout的action.运行代码,你会发现结果并没有变化,只是listener 得到消息的时间推迟了2000毫秒,但不同的是,logger是实时性的.

所以compose.js让中间件的执行顺序是 timeoutScheduler( logger(dispatch) )的.

把使用的顺序颠倒一下:

const createStoreWithMiddleware = applyMiddleware(timeoutScheduler,logger)(createStore);

将示例的代码改成这样. 你会发现 logger 执行顺序变慢了2000毫秒.
因为现在的执行是: logger( timeoutScheduler(dispatch) )

所以可以得出结论: compose.js 中执行顺序是 数组中排在前面的方法最先执行,执行完毕后将 dispatch函数 传递给后一个执行. 并且在前面执行的中间件将影响其后的中间件.


相关链接

官方gitbook: http://cn.redux.js.org/docs/advanced/Middleware.html

解读redux工作原理: http://blog.csdn.net/jhqdlove/article/details/51940400

Redux系列x:http://www.cnblogs.com/dh-dh/p/5350352.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 一、什么情况需要redux? 1、用户的使用方式复杂 2、不同身份的用户有不同的使用方式(比如普通用户和管...
    初晨的笔记阅读 2,011评论 0 11
  • http://gaearon.github.io/redux/index.html ,文档在 http://rac...
    jacobbubu阅读 79,887评论 35 198
  • 上周六参加了一个公司的面试,因为是很长时间以来的第一次面试,发挥的并不是很好,有两个下来一想就明白的问题在当时却卡...
    夏尔先生阅读 6,403评论 0 15
  • 用法 为了对中间件有一个整体的认识,先从用法开始分析。调用中间件的代码如下: 源码 createStore.js#...
    黄子毅阅读 9,566评论 4 19
  • 每天早上,在小区里,可以见到很多蜘蛛,它们多静静地趴在自己结的网中央。 我观察到的蜘蛛是8条腿,这和我常观察的其他...
    自由心空阅读 523评论 0 2