Skip to content

react-redux-source-code-read

Posted on:July 3, 2018 at 05:36 PM

Redux-react 源码解析

react 提供了 view 层的依据数据刷新,redux 提供了可追溯的数据流体系,但是如何将两者很好的整合起来呢?

react-redux,通过 connect 来连接组件和 store

订阅流程图

react-redux 修改浏览器缩放率可以查看大图。

从 Provider 开始

Provider 组件十分简单,接受参数 store,并将 store 作为上下文内容进行广播。

Provider.propTypes = {
  store: storeShape.isRequired,
  children: PropTypes.element.isRequired,
};
Provider.childContextTypes = {
  [storeKey]: storeShape.isRequired,
  [subscriptionKey]: subscriptionShape,
};

可以看到 provider 必须是有子组件的,不然它将没有任何意义。 同时它会广播 subscription,但是值为 Null,是为了区分 subcription 来源做的 hack 处理。

Connet 方法

connect

connect(
    mapStateToProps
    mapDispatchToProps
    mergeProps
    {
        ======== 默认参数 ========
        pure = true,
        areStatesEqual = strictEqual,
        areOwnPropsEqual = shallowEqual,
        areStatePropsEqual = shallowEqual,
        areMergedPropsEqual = shallowEqual,
        =========================
        ...extraOptions // 可以覆盖connect的参数,也可以覆盖connectHOC的参数
    }

    return connectAdvance(props)

)

connect 方法接收了三个主要函数用来描述组件与 store 之间的数据依赖,并进行了验证处理,保证三个传入参数符合要求。 connect 方法可以说是 connectAdvance 的简单配置版,默认提供了大部分 selectorFactory 需要的参数。

mapStateToProps

描述了组件与 state 之间的依赖,即需要哪些 state 中的属性 接受两个参数,state, ownProps

const mapStateToProps = state => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter),
  };
};

mapDispatchToProps

接受两个参数(dispath,和 ownProps) 为组件的 props 上添加预设好的 dispath-action

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    onClick: () => {
      dispatch({
        type: "SET_VISIBILITY_FILTER",
        filter: ownProps.filter,
      });
    },
  };
};

如果传入了这个参数,那么我们就可以在组件中使用this.props.onClick()就可以派发一个已经组装的  好的 action

mergeProps

接受三个参数(stateProps, dispatchProps, ownProps) 描述了计算后的 state,dispatchProps,和之前的 props 是如何组合起来的 默认的组合方式是浅拷贝三个数据,并组成一个新的对象,即{...stateProps, ...dispatchProps, ...ownProps}

比对等级

默认的 state 比较是采用严格对比,也就是 a === b, 而其它属性则使用了浅比较。 只比较了 OwnProperty 相等即可。

const hasOwn = Object.prototype.hasOwnProperty;

function is(x, y) {
  if (x === y) {
    return x !== 0 || y !== 0 || 1 / x === 1 / y;
  } else {
    return x !== x && y !== y;
  }
}

export default function shallowEqual(objA, objB) {
  if (is(objA, objB)) return true;

  if (
    typeof objA !== "object" ||
    objA === null ||
    typeof objB !== "object" ||
    objB === null
  ) {
    return false;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) return false;

  for (let i = 0; i < keysA.length; i++) {
    if (!hasOwn.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
      return false;
    }
  }

  return true;
}

ConnectAdvance

connectAdvanced(
    selectorFactory,(connect提供)
  {
    ...options
  }
){
  retrun wrapWithConnect(WrappedComponent){
    class connect extends React.Component {
      ...
      render(
        <wrapWithConnect {...props} />
      )
    }
    return connect
  }
}

ConnectAdvance 会根据传入参数返回 wrap 方法,这个方法接收一个 element 组件。最终再返回一个普通的 connect 组件用来管理订阅和更新。

订阅的实现:subscribution

在返回的 connect 组件中,在初始化的过程中会初始化两个对象,updater 和 subsciption

constructor(props, context) {

  ...

  this.state = {
    updater: this.createUpdater() // 初始化updater,下面会讲
  }
  this.initSubscription() // 初始化订阅
}

下面来看一看 subscription 的实现

initSubscription() {
  if (!shouldHandleStateChanges) return // 没有mapsToProps则代表不需要监听,直接取消订阅,updater也就失去了意义
  const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey] // 如果父级已经有订阅,则直接订阅父级,从而实现父级刷新再刷新子级的逻辑
  this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
  // hack处理,因为订阅派发的过程可能会出现组件卸载的情况,所以,卸载前将订阅清空是必须的
  this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
}

订阅类长什么样?

export default class Subscription {
  constructor(store, parentSub, onStateChange) {
    this.store = store;
    this.parentSub = parentSub;
    this.onStateChange = onStateChange;
    this.unsubscribe = null;
    this.listeners = nullListeners;
  }

  addNestedSub(listener) {
    this.trySubscribe();
    return this.listeners.subscribe(listener);
  }

  notifyNestedSubs() {
    this.listeners.notify();
  }

  isSubscribed() {
    return Boolean(this.unsubscribe);
  }

  trySubscribe() {
    if (!this.unsubscribe) {
      this.unsubscribe = this.parentSub
        ? this.parentSub.addNestedSub(this.onStateChange)
        : this.store.subscribe(this.onStateChange);

      this.listeners = createListenerCollection();
    }
  }

  tryUnsubscribe() {
    if (this.unsubscribe) {
      this.unsubscribe();
      this.unsubscribe = null;
      this.listeners.clear();
      this.listeners = nullListeners;
    }
  }
}

可以看到订阅类实现了类似 redux 的订阅体系,但是要更复杂,订阅不仅要去store订阅onStateChange函数,还要管理可能存在的子组件订阅,可以看到实现的核心是trySubscribe,分两种情况去管理订阅。如果有parentSub,则将onStateChange注册到父级的 listeners 中,没有则直接在store中 subscribe。

那么问题又来了,什么时候开始订阅?以及 onStateChange函数到底干了什么?

 componentDidMount() {
  if (!shouldHandleStateChanges) return
  this.subscription.trySubscribe()
  this.runUpdater()
 }

 onStateChange() {
   this.runUpdater(this.notifyNestedSubs)
 }

在组件加载完成后,组件开始了订阅,并且进行了一次手动更新。 而onStateChange则是  将子组件的订阅作为回调传入runUpdater

所以runUpdater又干了什么?

runUpdater(callback = noop) {
  if (this.isUnmounted) {
    return
  }
  this.setState(prevState => prevState.updater(this.props, prevState), callback)
}

刷新的关键就在这里,通过 setState,我们将  通过Updater计算的结果传入,从而进行更新,并在更新完成之后再进行子组件的更新。

更新的实现:updater & SelectFactory

上一节我们看到,在组件初始化的时候,我们注册了 Updater,那么他是怎么实现的呢?

createUpdater() {
  const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
  return makeUpdater(sourceSelector, this.store)
}
// 这个是组件外部封装的方法
function makeUpdater(sourceSelector, store) {
  return function updater(props, prevState) {
    try {
      const nextProps = sourceSelector(store.getState(), props)
      if (nextProps !== prevState.props || prevState.error) {
        return {
          shouldComponentUpdate: true,
          props: nextProps,
          error: null,
        }
      }
      return {
        shouldComponentUpdate: false,
      }
    } catch (error) {
      return {
        shouldComponentUpdate: true,
        error,
      }
    }
  }
}
export default function finalPropsSelectorFactory(
  dispatch,
  { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }
) {
  const mapStateToProps = initMapStateToProps(dispatch, options);
  const mapDispatchToProps = initMapDispatchToProps(dispatch, options);
  const mergeProps = initMergeProps(dispatch, options);

  if (process.env.NODE_ENV !== "production") {
    verifySubselectors(
      mapStateToProps,
      mapDispatchToProps,
      mergeProps,
      options.displayName
    );
  }

  const selectorFactory = options.pure
    ? pureFinalPropsSelectorFactory
    : impureFinalPropsSelectorFactory;

  return selectorFactory(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    dispatch,
    options
  );
}

可以看到传入的主要参数是 dispatch,initMapStateToProps,initMapDispatchToProps,initMergePropspure,connect组件中传入的三个匹配的方法需要传入dispatchoption才能使用

selectFactory 最后返回的也是

return function pureFinalPropsSelector(nextState, nextOwnProps) {
  return hasRunAtLeastOnce
    ? handleSubsequentCalls(nextState, nextOwnProps)
    : handleFirstCall(nextState, nextOwnProps);
};

接受 state,和 props,并与自己缓存的 ownProps, state 比对后返回整合后的 nextProps 结合makeUpdater所以上面接受的 state 是 store 的 state, props 是 updater 传入的,也就是当前 connect 组件的 props

export function pureFinalPropsSelectorFactory(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  dispatch,
  { areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
  let hasRunAtLeastOnce = false;
  let state;
  let ownProps;
  let stateProps;
  let dispatchProps;
  let mergedProps;

  function handleFirstCall(firstState, firstOwnProps) {
    state = firstState;
    ownProps = firstOwnProps;
    stateProps = mapStateToProps(state, ownProps);
    dispatchProps = mapDispatchToProps(dispatch, ownProps);
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
    hasRunAtLeastOnce = true;
    return mergedProps;
  }

  function handleNewPropsAndNewState() {
    stateProps = mapStateToProps(state, ownProps);

    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch, ownProps);

    mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
    return mergedProps;
  }

  function handleNewProps() {
    if (mapStateToProps.dependsOnOwnProps)
      stateProps = mapStateToProps(state, ownProps);

    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch, ownProps);

    mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
    return mergedProps;
  }

  function handleNewState() {
    const nextStateProps = mapStateToProps(state, ownProps);
    const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps);
    stateProps = nextStateProps;

    if (statePropsChanged)
      mergedProps = mergeProps(stateProps, dispatchProps, ownProps);

    return mergedProps;
  }

  function handleSubsequentCalls(nextState, nextOwnProps) {
    const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps);
    const stateChanged = !areStatesEqual(nextState, state);
    state = nextState;
    ownProps = nextOwnProps;

    if (propsChanged && stateChanged) return handleNewPropsAndNewState();
    if (propsChanged) return handleNewProps();
    if (stateChanged) return handleNewState();
    return mergedProps;
  }

  return function pureFinalPropsSelector(nextState, nextOwnProps) {
    return hasRunAtLeastOnce
      ? handleSubsequentCalls(nextState, nextOwnProps)
      : handleFirstCall(nextState, nextOwnProps);
  };
}

通常我们使用的就是 pureFinalPropsSelectorFactory,通过缓存之前的 state 和 props 和传入的 state,props 进行指定的比对,从而判断是否需要更新,以及怎么样  更新。

被包裹组件的更新

updater 运行之后,setState 只是改变 connect 组件的 props,被包裹的组件时怎么更新的呢?

addExtraProps(props) {
  if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props
  const withExtras = { ...props }
  if (withRef) withExtras.ref = this.setWrappedInstance
  if (renderCountProp) withExtras[renderCountProp] = this.renderCount++
  if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription
  return withExtras
}

render() {
  if (this.state.error) {
    throw this.state.error
  } else {
    return createElement(WrappedComponent, this.addExtraProps(this.state.props))
  }
}

可以看到,render 函数是将被包裹的组件注入了所有的 Props 后返回的,所以只要 connect 的 props 发生了变化,被包裹的组件就会相应的刷新。

静态属性相关

为了不影响到子组件的内部实现,connect 组件将子组件的静态属性进行了拷贝,防止 propType,contextType 这些属性的失效,实现的途径是使用第三方类库hoist-non-react-statics

Connect.WrappedComponent = WrappedComponent; // 最原始的被包裹组件
Connect.displayName = displayName; // 组件的现实名称
Connect.childContextTypes = childContextTypes; // 子级上线文限制
Connect.contextTypes = contextTypes; // 自身上下文限制
Connect.propTypes = contextTypes; // 自身属性限制,会和子组件属性进行合并
Connect.getDerivedStateFromProps = getDerivedStateFromProps; // react 新增生命周期,返回更新后的state,用来更新组件

return hoistStatics(Connect, WrappedComponent);

包裹组件如何获取

connectAdvance中,withRef 为 true 时,connect 组件会将 wrapComponent 的 ref 属性覆盖,将 ref 的回调替换为

setWrappedInstance(ref) {
    this.wrappedInstance = ref
}

所以当引用当前 connect 组建时可以通过,获取被包裹组件的实例

getWrappedInstance() {
    invariant(withRef,
        `To access the wrapped instance, you need to specify ` +
        `{ withRef: true } in the options argument of the ${methodName}() call.`
    )
    return this.wrappedInstance
}

通过当前 api 也可以获取,但是这是最原始的组件,并不是实例 Connect.WrappedComponent = WrappedComponent

shouldHandleStateChange 和 pure 的区别

shouldHandleStateChange决定了是否需要订阅 pure则是决定了 selector 更新的方式,pure 为 false 时,组件将会跳过比较阶段,直接将所有属性更新。

propsMode

connect 组件没有强制规定只能使用 provider 提供的 store,即你可以直接将其它 store 直接通过 Props 传递给 connect 组件,但这就造成了订阅可能出现混乱的问题, 所以需要对 store 来自 props 的组件进行区分。

react-redux

初始化的区别

initSubscription() {
  if (!shouldHandleStateChanges) return

  // parentSub's source should match where store came from: props vs. context. A component
  // connected to the store via props shouldn't use subscription from context, or vice versa.
  const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
  this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))

  this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
}

源码也进行了解释,订阅需要和它的来源相对应,即 store 从 props 来,他的订阅也应该是 prop 上的,反之亦然

上下文的区别

getChildContext() {
  // If this component received store from props, its subscription should be transparent
  // to any descendants receiving store+subscription from context; it passes along
  // subscription passed to it. Otherwise, it shadows the parent subscription, which allows
  // Connect to control ordering of notifications to flow top-down.
  const subscription = this.propsMode ? null : this.subscription
  return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
}

connect 组件会将 subsciption 在上下文上传递,但如果是通过 props 传入 store 的组件是不需要向下传递的,他的订阅应该是透明的。

wrapElementProps 的区别

addExtraProps(props) {
  if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props
  // make a shallow copy so that fields added don't leak to the original selector.
  // this is especially important for 'ref' since that's a reference back to the component
  // instance. a singleton memoized selector would then be holding a reference to the
  // instance, preventing the instance from being garbage collected, and that would be bad
  const withExtras = { ...props }
  if (withRef) withExtras.ref = this.setWrappedInstance
  if (renderCountProp) withExtras[renderCountProp] = this.renderCount++
  if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription
  return withExtras
}

可以看到,propsMode 的订阅会通过 props 的模式传递给被包裹的组件,如果对源码熟悉的话,可以进行一系列的操作。

后记

reduxreact-redux组合起来,数据管理,以及组件的  数据订阅已经相当完善了,但是还有一个问题没有解决,如何更好的处理异步请求 ? redux-saga是目前最受欢迎的异步处理框架,因为它没有破坏 dispatch 的封装性,并且能够以同步代码的形式书写异步代码,下一集我们再来讨论它吧。