初见

redux v0.2.1源码学习

2019-08-25  本文已影响0人  没有颜色的菜

前言

这周突然想学习一下状态管理的写法。看看业界是怎么实现的,之前使用过 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

最后

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

上一篇下一篇

猜你喜欢

热点阅读