React-Redux源码分析

Redux,做为大型React应用状态管理最经常使用的工具,其概念理论和实践都是很值得咱们学习,分析而后在实践中深刻了解的,对前端开发者能力成长颇有帮助。本篇计划结合Redux容器组件和展现型组件的区别对比以及Redux与React应用最多见的链接库,react-redux源码分析,以期达到对Redux和React应用的更深层次理解。javascript

欢迎访问个人我的博客html

前言

react-redux库提供Provider组件经过context方式向应用注入store,而后可使用connect高阶方法,获取并监听store,而后根据store state和组件自身props计算获得新props,注入该组件,而且能够经过监听store,比较计算出的新props判断是否须要更新组件。前端

react与redux应用结构
react与redux应用结构

Provider

首先,react-redux库提供Provider组件将store注入整个React应用的某个入口组件,一般是应用的顶层组件。Provider组件使用context向下传递store:java

// 内部组件获取redux store的键
const storeKey = 'store'
// 内部组件
const subscriptionKey = subKey || `${storeKey}Subscription`
class Provider extends Component {
  // 声明context,注入store和可选的发布订阅对象
  getChildContext() {
    return { [storeKey]: this[storeKey], [subscriptionKey]: null }
  }

  constructor(props, context) {
    super(props, context)
    // 缓存store
    this[storeKey] = props.store;
  }

  render() {
    // 渲染输出内容
    return Children.only(this.props.children)
  }
}复制代码

Example

import { Provider } from 'react-redux'
import { createStore } from 'redux'
import App from './components/App'
import reducers from './reducers'

// 建立store
const store = createStore(todoApp, reducers)

// 传递store做为props给Provider组件;
// Provider将使用context方式向下传递store
// App组件是咱们的应用顶层组件
render(
  <Provider store={store}>
    <App/>
  </Provider>, document.getElementById('app-node')
)复制代码

connect方法

在前面咱们使用Provider组件将redux store注入应用,接下来须要作的是链接组件和store。并且咱们知道Redux不提供直接操做store state的方式,咱们只能经过其getState访问数据,或经过dispatch一个action来改变store state。node

这也正是react-redux提供的connect高阶方法所提供的能力。react

Example

container/TodoList.js

首先咱们建立一个列表容器组件,在组件内负责获取todo列表,而后将todos传递给TodoList展现型组件,同时传递事件回调函数,展现型组件触发诸如点击等事件时,调用对应回调,这些回调函数内经过dispatch actions来更新redux store state,而最终将store和展现型组件链接起来使用的是react-redux的connect方法,该方法接收git

import {connect} from 'react-redux'
import TodoList from 'components/TodoList.jsx'

class TodoListContainer extends React.Component {
  constructor(props) {
    super(props)
    this.state = {todos: null, filter: null}
  }
  handleUpdateClick (todo) {
    this.props.update(todo);  
  }
  componentDidMount() {
    const { todos, filter, actions } = this.props
    if (todos.length === 0) {
      this.props.fetchTodoList(filter);
    }
  render () {
    const { todos, filter } = this.props

    return (
      <TodoList 
        todos={todos}
        filter={filter}
        handleUpdateClick={this.handleUpdateClick}
        /* others */
      />
    )
  }
}

const mapStateToProps = state => {
  return {
    todos : state.todos,
    filter: state.filter
  }
}

const mapDispatchToProps = dispatch => {
  return {
    update : (todo) => dispatch({
      type : 'UPDATE_TODO',
      payload: todo
    }),
    fetchTodoList: (filters) => dispatch({
      type : 'FETCH_TODOS',
      payload: filters
    })
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoListContainer)复制代码

components/TodoList.js

import React from 'react'
import PropTypes from 'prop-types'
import Todo from './Todo'

const TodoList = ({ todos, handleUpdateClick }) => (
  <ul>
    {todos.map(todo => (
      <Todo key={todo.id} {...todo} handleUpdateClick={handleUpdateClick} />
    ))}
  </ul>
)

TodoList.propTypes = {
  todos: PropTypes.array.isRequired
  ).isRequired,
  handleUpdateClick: PropTypes.func.isRequired
}

export default TodoList复制代码

components/Todo.js

import React from 'react'
import PropTypes from 'prop-types'

class Todo extends React.Component { 
  constructor(...args) {
    super(..args);
    this.state = {
      editable: false,
      todo: this.props.todo
    }
  }
  handleClick (e) {
    this.setState({
      editable: !this.state.editable
    })
  }
  update () {
    this.props.handleUpdateClick({
      ...this.state.todo
      text: this.refs.content.innerText
    })
  }
  render () {
    return (
      <li
        onClick={this.handleClick}
        style={{
          contentEditable: editable ? 'true' : 'false'
        }}
      >
        <p ref="content">{text}</p>
        <button onClick={this.update}>Save</button>
      </li>
    )
  }

Todo.propTypes = {
  handleUpdateClick: PropTypes.func.isRequired,
  text: PropTypes.string.isRequired
}

export default Todo复制代码

容器组件与展现型组件

在使用Redux做为React应用的状态管理容器时,一般贯彻将组件划分为容器组件(Container Components)和展现型组件(Presentational Components)的作法,github

Presentational Components Container Components
目标 UI展现 (HTML结构和样式) 业务逻辑(获取数据,更新状态)
感知Redux
数据来源 props 订阅Redux store
变动数据 调用props传递的回调函数 Dispatch Redux actions
可重用 独立性强 业务耦合度高

应用中大部分代码是在编写展现型组件,而后使用一些容器组件将这些展现型组件和Redux store链接起来。redux

connect()源码分析

react-redux源码逻辑
react-redux源码逻辑

connectHOC = connectAdvanced;
mergePropsFactories = defaultMergePropsFactories;
selectorFactory = defaultSelectorFactory;
function connect (
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  {
  pure = true,
  areStatesEqual = strictEqual, // 严格比较是否相等
  areOwnPropsEqual = shallowEqual, // 浅比较
  areStatePropsEqual = shallowEqual,
  areMergedPropsEqual = shallowEqual,
  renderCountProp, // 传递给内部组件的props键,表示render方法调用次数
  // props/context 获取store的键
  storeKey = 'store',
  ...extraOptions
  } = {}
) {
  const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps')
  const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')
  const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')

  // 调用connectHOC方法
  connectHOC(selectorFactory, {
    // 若是mapStateToProps为false,则不监听store state
    shouldHandleStateChanges: Boolean(mapStateToProps),
    // 传递给selectorFactory
    initMapStateToProps,
    initMapDispatchToProps,
    initMergeProps,
    pure,
    areStatesEqual,
    areOwnPropsEqual,
    areStatePropsEqual,
    areMergedPropsEqual,
    renderCountProp, // 传递给内部组件的props键,表示render方法调用次数
    // props/context 获取store的键
    storeKey = 'store',
    ...extraOptions // 其余配置项
  });
}复制代码

strictEquall

function strictEqual(a, b) { return a === b }复制代码

shallowEquall

源码数组

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
}复制代码
shallowEqual({x:{}},{x:{}}) // false
shallowEqual({x:1},{x:1}) // true复制代码

connectAdvanced高阶函数

源码

function connectAdvanced (
  selectorFactory,
  {
    renderCountProp = undefined, // 传递给内部组件的props键,表示render方法调用次数
    // props/context 获取store的键
    storeKey = 'store',
    ...connectOptions
  } = {}
) {
  // 获取发布订阅器的键
  const subscriptionKey = storeKey + 'Subscription';
  const contextTypes = {
    [storeKey]: storeShape,
    [subscriptionKey]: subscriptionShape,
  };
  const childContextTypes = {
    [subscriptionKey]: subscriptionShape,
  };

  return function wrapWithConnect (WrappedComponent) {
    const selectorFactoryOptions = {
      // 若是mapStateToProps为false,则不监听store state
      shouldHandleStateChanges: Boolean(mapStateToProps),
      // 传递给selectorFactory
      initMapStateToProps,
      initMapDispatchToProps,
      initMergeProps,
      ...connectOptions,
      ...others
      renderCountProp, // render调用次数
      shouldHandleStateChanges, // 是否监听store state变动
      storeKey,
      WrappedComponent
    }

    // 返回拓展过props属性的Connect组件
    return hoistStatics(Connect, WrappedComponent)
  }
}复制代码

selectorFactory

selectorFactory函数返回一个selector函数,根据store state, 展现型组件props,和dispatch计算获得新props,最后注入容器组件,selectorFactory函数结构形如:

(dispatch, options) => (state, props) => ({
  thing: state.things[props.thingId],
  saveThing: fields => dispatch(actionCreators.saveThing(props.thingId, fields)),
})复制代码

注:redux中的state一般指redux store的state而不是组件的state,另此处的props为传入组件wrapperComponent的props。

源码

function defaultSelectorFactory (dispatch, {
  initMapStateToProps,
  initMapDispatchToProps,
  initMergeProps,
  ...options
}) {
  const mapStateToProps = initMapStateToProps(dispatch, options)
  const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
  const mergeProps = initMergeProps(dispatch, options)

  // pure为true表示selectorFactory返回的selector将缓存结果;
  // 不然其老是返回一个新对象
  const selectorFactory = options.pure
    ? pureFinalPropsSelectorFactory
    : impureFinalPropsSelectorFactory

  // 最终执行selector工厂函数返回一个selector
  return selectorFactory(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    dispatch,
    options
  );
}复制代码

pureFinalPropsSelectorFactory

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

  // 返回合并后的props或state
  // handleSubsequentCalls变动后合并;handleFirstCall初次调用
  return function pureFinalPropsSelector(nextState, nextOwnProps) {
    return hasRunAtLeastOnce
      ? handleSubsequentCalls(nextState, nextOwnProps)
    : handleFirstCall(nextState, nextOwnProps)
  }  
}复制代码

handleFirstCall

function handleFirstCall(firstState, firstOwnProps) {
  state = firstState
  ownProps = firstOwnProps
  stateProps = mapStateToProps(state, ownProps) // store state映射到组件的props
  dispatchProps = mapDispatchToProps(dispatch, ownProps)
  mergedProps = mergeProps(stateProps, dispatchProps, ownProps) // 合并后的props
  hasRunAtLeastOnce = true
  return mergedProps
}复制代码

defaultMergeProps

export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
  // 默认合并props函数
  return { ...ownProps, ...stateProps, ...dispatchProps }
}复制代码

handleSubsequentCalls

function handleSubsequentCalls(nextState, nextOwnProps) {
  // shallowEqual浅比较
  const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
  // 深比较
  const stateChanged = !areStatesEqual(nextState, state)
  state = nextState
  ownProps = nextOwnProps

  // 处理props或state变动后的合并
  // store state及组件props变动
  if (propsChanged && stateChanged) return handleNewPropsAndNewState()
  if (propsChanged) return handleNewProps()
  if (stateChanged) return handleNewState()

  return mergedProps
}复制代码

计算返回新props

只要展现型组件自身props发生变动,则须要从新返回新合并props,而后更新容器组件,不管store state是否变动:

// 只有展现型组件props变动
function handleNewProps() {
  // mapStateToProps计算是否依赖于展现型组件props
  if (mapStateToProps.dependsOnOwnProps)
    stateProps = mapStateToProps(state, ownProps)
  // mapDispatchToProps计算是否依赖于展现型组件props
  if (mapDispatchToProps.dependsOnOwnProps)
    dispatchProps = mapDispatchToProps(dispatch, ownProps)

  mergedProps = mergeProps(stateProps, dispatchProps, ownProps)

  return mergedProps
}
// 展现型组件props和store state均变动
function handleNewPropsAndNewState() {
  stateProps = mapStateToProps(state, ownProps)
  // mapDispatchToProps计算是否依赖于展现型组件props
  if (mapDispatchToProps.dependsOnOwnProps)
    dispatchProps = mapDispatchToProps(dispatch, ownProps)

  mergedProps = mergeProps(stateProps, dispatchProps, ownProps)

  return mergedProps
}复制代码

计算返回stateProps

一般容器组件props变动由store state变动推进,因此只有store state变动的状况较多,并且此处也正是使用Immutable时须要注意的地方:不要在mapStateToProps方法内使用toJS()方法。

mapStateToProps两次返回的props对象未有变动时,不须要从新计算,直接返回以前合并获得的props对象便可,以后在selector追踪对象中比较两次selector函数返回值是否有变动时,将返回false,容器组件不会触发变动。

由于对比屡次mapStateToProps返回的结果时是使用浅比较,因此不推荐使用Immutable.toJS()方法,其每次均返回一个新对象,对比将返回false,而若是使用Immutable且其内容未变动,则会返回true,能够减小没必要要的从新渲染。

// 只有store state变动
function handleNewState() {
  const nextStateProps = mapStateToProps(state, ownProps)
  // 浅比较
  const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)
  stateProps = nextStateProps

  // 计算获得的新props变动了,才须要从新计算返回新的合并props
  if (statePropsChanged) {
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
  }

  // 若新stateProps未发生变动,则直接返回上一次计算得出的合并props;
  // 以后selector追踪对象比较两次返回值是否有变动时将返回false;
  // 不然返回使用mergeProps()方法新合并获得的props对象,变动比较将返回true
  return mergedProps
}复制代码

hoist-non-react-statics

相似Object.assign,将子组件的非React的静态属性或方法复制到父组件,React相关属性或方法不会被覆盖而是合并。

hoistStatics(Connect, WrappedComponent)复制代码

Connect Component

真正的Connect高阶组件,链接redux store state和传入组件,即将store state映射到组件props,react-redux使用Provider组件经过context方式注入store,而后Connect组件经过context接收store,并添加对store的订阅:

class Connect extends Component {
  constructor(props, context) {
    super(props, context)

    this.state = {}
    this.renderCount = 0 // render调用次数初始为0
    // 获取store,props或context方式
    this.store = props[storeKey] || context[storeKey]
    // 是否使用props方式传递store
    this.propsMode = Boolean(props[storeKey])

    // 初始化selector
    this.initSelector()
    // 初始化store订阅
    this.initSubscription()
  }

  componentDidMount() {
    // 不须要监听state变动
    if (!shouldHandleStateChanges) return
    // 发布订阅器执行订阅
    this.subscription.trySubscribe()
    // 执行selector
    this.selector.run(this.props)
    // 若还须要更新,则强制更新
    if (this.selector.shouldComponentUpdate) this.forceUpdate()
  }

  // 渲染组件元素
  render() {
    const selector = this.selector
    selector.shouldComponentUpdate = false; // 重置是否须要更新为默认的false

    // 将redux store state转化映射获得的props合并入传入的组件
    return createElement(WrappedComponent, this.addExtraProps(selector.props))
  }
}复制代码

addExtraProps()

给props添加额外的props属性:

// 添加额外的props
addExtraProps(props) {
  const withExtras = { ...props }
  if (renderCountProp) withExtras[renderCountProp] = this.renderCount++;// render 调用次数
  if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription

  return withExtras
}复制代码

初始化selector追踪对象initSelector

Selector,选择器,根据redux store state和组件的自身props,计算出将注入该组件的新props,并缓存新props,以后再次执行选择器时经过对比得出的props,决定是否须要更新组件,若props变动则更新组件,不然不更新。

使用initSelector方法初始化selector追踪对象及相关状态和数据:

// 初始化selector
initSelector() {
  // 使用selector工厂函数建立一个selector
  const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
  // 链接组件的selector和redux store state
  this.selector = makeSelectorStateful(sourceSelector, this.store)
  // 执行组件的selector函数
  this.selector.run(this.props)
}复制代码

makeSelectorStateful()

建立selector追踪对象以追踪(tracking)selector函数返回结果:

function makeSelectorStateful(sourceSelector, store) {
  // 返回selector追踪对象,追踪传入的selector(sourceSelector)返回的结果
  const selector = {
    // 执行组件的selector函数
    run: function runComponentSelector(props) {
      // 根据store state和组件props执行传入的selector函数,计算获得nextProps
      const nextProps = sourceSelector(store.getState(), props)
      // 比较nextProps和缓存的props;
      // false,则更新所缓存的props并标记selector须要更新
      if (nextProps !== selector.props || selector.error) {
        selector.shouldComponentUpdate = true // 标记须要更新
        selector.props = nextProps // 缓存props
        selector.error = null
      }  
    }
  }

  // 返回selector追踪对象
  return selector
}复制代码

初始化订阅initSubscription

初始化监听/订阅redux store state:

// 初始化订阅
initSubscription() {
  if (!shouldHandleStateChanges) return; // 不须要监听store state

  // 判断订阅内容传递方式:props或context,二者不能混杂
  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使用的订阅发布器实现:

export default class Subscription {
  constructor(store, parentSub, onStateChange) {
    // redux store
    this.store = store
    // 订阅内容
    this.parentSub = parentSub
    // 订阅内容变动后的回调函数
    this.onStateChange = onStateChange
    this.unsubscribe = null
    // 订阅记录数组
    this.listeners = nullListeners
  }

  // 订阅
  trySubscribe() {
    if (!this.unsubscribe) {
      // 若传递了发布订阅器则使用该订阅器订阅方法进行订阅
      // 不然使用store的订阅方法
      this.unsubscribe = this.parentSub
        ? this.parentSub.addNestedSub(this.onStateChange)
        : this.store.subscribe(this.onStateChange)

      // 建立订阅集合对象
      // { notify: function, subscribe: function }
      // 内部包装了一个发布订阅器;
      // 分别对应发布(执行全部回调),订阅(在订阅集合中添加回调)
      this.listeners = createListenerCollection()
    }
  }

  // 发布
  notifyNestedSubs() {
    this.listeners.notify()
  }
}复制代码

订阅回调函数

订阅后执行的回调函数:

onStateChange() {
  // 选择器执行
  this.selector.run(this.props)

  if (!this.selector.shouldComponentUpdate) {
    // 不须要更新则直接发布
    this.notifyNestedSubs()
  } else {
    // 须要更新则设置组件componentDidUpdate生命周期方法
    this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
    // 同时调用setState触发组件更新
    this.setState(dummyState) // dummyState = {}
  }
}

// 在组件componentDidUpdate生命周期方法内发布变动
notifyNestedSubsOnComponentDidUpdate() {
  // 清除组件componentDidUpdate生命周期方法
  this.componentDidUpdate = undefined
  // 发布
  this.notifyNestedSubs()
}复制代码

其余生命周期方法

getChildContext () {
  // 若存在props传递了store,则须要对其余从context接收store并订阅的后代组件隐藏其对于store的订阅;
  // 不然将父级的订阅器映射传入,给予Connect组件控制发布变化的顺序流
  const subscription = this.propsMode ? null : this.subscription
  return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
}
// 接收到新props
componentWillReceiveProps(nextProps) {
  this.selector.run(nextProps)
}

// 是否须要更新组件
shouldComponentUpdate() {
  return this.selector.shouldComponentUpdate
}

componentWillUnmount() {
  // 重置selector
}复制代码

参考阅读

  1. React with redux
  2. Smart and Dumb Components
  3. React Redux Container Pattern-
相关文章
相关标签/搜索