美文网首页初见
redux v0.2.1源码学习

redux v0.2.1源码学习

作者: 没有颜色的菜 | 来源:发表于2019-08-25 19:36 被阅读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

    • 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 }
      

    最后

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

    相关文章

      网友评论

        本文标题:redux v0.2.1源码学习

        本文链接:https://www.haomeiwen.com/subject/dcnhectx.html