redux v0.2.1源码学习

前言

这周突然想学习一下状态管理的写法。看看业界是怎么实现的,之前使用过 redux,那就先从 redux 下手吧,但是,一上来就看最新版本的代码,不太适合新手学习,一方面最新版本已经发展n多年了,功能已经非常完善(代码多难懂),另一方面直接看最新的不了解这个工具是怎么设计出来的。于是就打算学习最早的发布版本 v0.2.1

先来说下我认识的一般的状态管理的基本路子:

全局只存在 唯一state,而前端不直接改变 state,而是通过 action 去改变 state

HelloWorld

一个计数器的栗子,目录结构如下:

counter
├── App.js
├── Counter.js
├── actions
│   ├── CounterActions.js
│   └── index.js
├── constants
│   └── ActionTypes.js
├── dispatcher.js
└── stores
    ├── CounterStore.js
    └── index.js

actions

函数,返回一个带 type 的对象,或者返回一个函数

import {
  INCREMENT_COUNTER,
  DECREMENT_COUNTER
} from '../constants/ActionTypes';

export function increment() {
  return {
    type: INCREMENT_COUNTER
  };
}

export function incrementAsync() {
  return dispatch => {
    setTimeout(() => {
      dispatch(increment());
    }, 1000);
  };
}

export function decrement() {
  return {
    type: DECREMENT_COUNTER
  };
}

store

返回一个函数,参数 state 和 action,当 state 为空时返回初始值,表示初始化。根据 action 的 type 值,进行相应的做法,返回一个新的 state。

import {
  INCREMENT_COUNTER,
  DECREMENT_COUNTER
} from '../constants/ActionTypes';

const initialState = { counter: 0 };

function incremenent({ counter }) {
  return { counter: counter + 1 };
}

function decremenent({ counter }) {
  return { counter: counter - 1 };
}

export default function CounterStore(state, action) {
  if (!state) {
    return initialState;
  }

  switch (action.type) {
  case INCREMENT_COUNTER:
    return incremenent(state, action);
  case DECREMENT_COUNTER:
    return decremenent(state, action);
  default:
    return state;
  }
}

入口 App.js 你会发现 @provides(dispatcher) 这个奇怪的东西,在 React 里面还经常出现,装饰器。

import React, { Component } from 'react';
import Counter from './Counter';
import { provides } from 'redux';
import dispatcher from './dispatcher';

@provides(dispatcher)
export default class App extends Component {
  render() {
    return (
      <Counter />
    );
  }
}

Couter.js,同样,也出现 performs(方法),observes(观察者)等关键字。使用 state 直接使用 this.props 解构赋值即可。

import React from 'react';
import { performs, observes } from 'redux';

@performs('increment', 'decrement')
@observes('CounterStore')
export default class Counter {
  render() {
    const { increment, decrement } = this.props;
    return (
      <p>
        Clicked: {this.props.counter} times
        {' '}
        <button onClick={() => increment()}>+</button>
        {' '}
        <button onClick={() => decrement()}>-</button>
      </p>
    );
  }
}

这些关键字是早起 Redux 状态管理的关键,现在的版本应该已经不使用这种方式了。

解析

dispatcher

通过 provides 将 dispatcher 注入到 App 中,其中,dispatcher 是通过 createDispatcher 创建,并调用了 dispatcher.receive(stores, actions) 进行绑定。

import * as stores from './stores/index';
import * as actions from './actions/index';
import { createDispatcher } from 'redux';

const dispatcher =
  module.hot && module.hot.data && module.hot.data.dispatcher ||
  createDispatcher();

dispatcher.receive(stores, actions);

module.hot.dispose(data => {
  data.dispatcher = dispatcher;
});

export default dispatcher;

receive 方法,actionCreator 将 action 进行封装,

// Provide a way to receive new stores and actions
  function receive(nextStores, nextActionCreators) {
    stores = nextStores;
    actionCreators = mapValues(nextActionCreators, wrapActionCreator);

    // Merge the observers
    observers = mapValues(stores,
      (store, key) => observers[key] || []
    );

    // Dispatch to initialize stores
    if (currentTransaction) {
      updateState(committedState);
      currentTransaction.forEach(dispatch);
    } else {
      dispatch(BOOTSTRAP_STORE);
    }
  }

action 进行转化返回一个 dispatchAction 函数,如果 action 为函数,则先执行函数,把 dispatchInTransaction 作为参数传入,这样可以在 action 内部使用该函数了,否则使用 dispatchInTransaction 函数调用。

 // Bind action creator to the dispatcher
  function wrapActionCreator(actionCreator) {
    return function dispatchAction(...args) {
      const action = actionCreator(...args);
      if (typeof action === 'function') {
        // Async action creator
        action(dispatchInTransaction);
      } else {
        // Sync action creator
        dispatchInTransaction(action);
      }
    };
  }

dispatchInTransaction ,执行 dispatch ,计算 nextState,执行 updateState 更新。

  // Dispatch in the context of current transaction
  function dispatchInTransaction(action) {
    if (currentTransaction) {
      currentTransaction.push(action);
    }
    dispatch(action);
  }

// Reassign the current state on each dispatch
  function dispatch(action) {
    if (typeof action.type !== 'string') {
      throw new Error('Action type must be a string.');
    }

    const nextState = computeNextState(currentState, action);
    updateState(nextState);
  }

获取 store,也就是 CounterStore,把参数传入,获取新的 state

  // To compute the next state, combine the next states of every store
  function computeNextState(state, action) {
    return mapValues(stores,
      (store, key) => store(state[key], action)
    );
  }

updateState 实现,计算变化的 changedKeys,执行 emitChange 进行更新。

  // Update state and emit change if needed
  function updateState(nextState) {
    // Swap the state
    const previousState = currentState;
    currentState = nextState;

    // Notify the observers
    const changedKeys = Object.keys(currentState).filter(key =>
      currentState[key] !== previousState[key]
    );
    emitChange(changedKeys);
  }

emitChange,获取需要通知的 observers,调用通知函数。

// Notify observers about the changed stores
  function emitChange(changedKeys) {
    if (!changedKeys.length) {
      return;
    }

    // Gather the affected observers
    const notifyObservers = [];
    changedKeys.forEach(key => {
      observers[key].forEach(o => {
        if (notifyObservers.indexOf(o) === -1) {
          notifyObservers.push(o);
        }
      });
    });

    // Emit change
    notifyObservers.forEach(o => o());
  }

这里可能有点疑问,obersevers 是什么,从哪来?往下看~

observes.js

将 组件进行装饰,构造函数中有一个

this.unobserve = this.context.observeStores(storeKeys, this.handleChange);
context 就是 dispatcher,

import React, { Component, PropTypes } from 'react';
import pick from 'lodash/object/pick';
import identity from 'lodash/utility/identity';

const contextTypes = {
  observeStores: PropTypes.func.isRequired
};

export default function connect(...storeKeys) {
  let mapState = identity;

  // Last argument may be a custom mapState function
  const lastIndex = storeKeys.length - 1;
  if (typeof storeKeys[lastIndex] === 'function') {
    [mapState] = storeKeys.splice(lastIndex, 1);
  }

  return function (DecoratedComponent) {
    const wrappedDisplayName =
      DecoratedComponent.displayName ||
      DecoratedComponent.name ||
      'Component';

    return class extends Component {
      static displayName = `ReduxObserves(${wrappedDisplayName})`;
      static contextTypes = contextTypes;

      constructor(props, context) {
        super(props, context);
        this.handleChange = this.handleChange.bind(this);
        this.unobserve = this.context.observeStores(storeKeys, this.handleChange);
      }
      ....

      componentWillUnmount() {
        this.unobserve();
      }

      render() {
        return (
          <DecoratedComponent {...this.props}
                              {...this.state} />
        );
      }
    };
  };
}

dispatcher observeStores 方法,将需要监听的组件传入,以及 onChange 函数,作为回调使用。最后返回一个函数,移除监听,这个也太妙了吧。

// Provide subscription and unsubscription
  function observeStores(observedKeys, onChange) {
    // Emit the state update
    function handleChange() {
      onChange(currentState);
    }

    // Synchronously emit the initial value
    handleChange();

    // Register the observer for each relevant key
    observedKeys.forEach(key =>
      observers[key].push(handleChange)
    );

    // Let it unregister when the time comes
    return () => {
      observedKeys.forEach(key => {
        const index = observers[key].indexOf(handleChange);
        observers[key].splice(index, 1);
      });
    };
  }

当计算好 nextState 后,就会调用 observe 的 onChange 方法, onChange 方法也就是 装饰器里面的方法。最后调用自身的 updateState,使用 setState 进行组件更新。而这些 state 作为 props 传入了我们自己的组件,也就可以通过 this.props 拿到。完美~~~


      handleChange(stateFromStores) {
        this.currentStateFromStores = pick(stateFromStores, storeKeys);
        this.updateState(stateFromStores, this.props);
      }

      componentWillReceiveProps(nextProps) {
        this.updateState(this.currentStateFromStores, nextProps);
      }

      updateState(stateFromStores, props) {
        if (storeKeys.length === 1) {
          // Just give it the particular store state for convenience
          stateFromStores = stateFromStores[storeKeys[0]];
        }

        const state = mapState(stateFromStores, props);
        if (this.state) {
          this.setState(state);
        } else {
          this.state = state;
        }
      }

performs 组件

action 绑定到组件,可以通过 this.props ,通过 this.context.getActions() 拿到 actions

import React, { Component, PropTypes } from 'react';
import pick from 'lodash/object/pick';
import identity from 'lodash/utility/identity';

const contextTypes = {
  getActions: PropTypes.func.isRequired
};

export default function performs(...actionKeys) {
  let mapActions = identity;

  // Last argument may be a custom mapState function
  const lastIndex = actionKeys.length - 1;
  if (typeof actionKeys[lastIndex] === 'function') {
    [mapActions] = actionKeys.splice(lastIndex, 1);
  }

  return function (DecoratedComponent) {
    const wrappedDisplayName =
      DecoratedComponent.displayName ||
      DecoratedComponent.name ||
      'Component';

    return class extends Component {
      static displayName = `ReduxPerforms(${wrappedDisplayName})`;
      static contextTypes = contextTypes;

      constructor(props, context) {
        super(props, context);
        this.updateActions(props);
      }

      componentWillReceiveProps(nextProps) {
        this.updateActions(nextProps);
      }

      updateActions(props) {
        this.actions = mapActions(
          pick(this.context.getActions(), actionKeys),
          props
        );
      }

      render() {
        return (
          <DecoratedComponent {...this.props}
                              {...this.actions} />
        );
      }
    };
  };
}

到这里就差不多了~
额外收获

Lodash

  • pick
    var object = { 'user': 'fred', 'age': 40 };
    _.pick(object, 'user');
    // => { 'user': 'fred' }
    _.pick(object, _.isString);
    // => { 'user': 'fred' }
    
  • identity
    function identity(value) {
      return value;
    }
    
  • mapValues
    _.mapValues({ 'a': 1, 'b': 2 }, function(n) {
     return n * 3;
    });
    // => { 'a': 3, 'b': 6 }
    

最后

麻雀虽小,却能看透精髓~

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

推荐阅读更多精彩内容