Connect

源码

首先把输入输出搞清楚,我们观察源码的return处,发现connect其实是一个curried function .

// 一共三个return
function createConnect() {
  return function connect() {
    return connectHOC()
  }
}

Line41 - Line45 我们可以看见createConnect的参数:

  connectHOC= connectAdvanced,

  mapStateToPropsFactories = defaultMapStateToPropsFactories,

  mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,

  mergePropsFactories = defaultMergePropsFactories,

  selectorFactory = defaultSelectorFactory

参数的第一行导入了一个叫connectAdvanced.js的文件,然后请注意Line9 - Line22, 向我们解释清楚了这个connectAdvanced和connect的关系。

我们真正的用户interface在第二层函数connect里,第一层函数为我们做了一些默认参数初始化:

  mapStateToPropsFactories = defaultMapStateToPropsFactories,
  mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
  mergePropsFactories = defaultMergePropsFactories,
  selectorFactory = defaultSelectorFactory

然后再在函数体内直接对参数进行验证:

const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps')
const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')

我们可以看出使用的是match这个函数,这个函数长这样:

function match(arg, factories, name) {
  for (let i = factories.length - 1; i >= 0; i--) {
    const result = factories[i](arg)
    if (result) return result
  }

  return (dispatch, options) => {
    throw new Error(`Invalid value of type ${typeof arg} for ${name} argument when connecting component ${options.wrappedComponentName}.`)
  }
}

也就说,如果要返回一个result的话,需要使得result在两次赋值中都为真值,具体是如何判断的呢?来看看mapStateToProps.js:

export function whenMapStateToPropsIsFunction(mapStateToProps) {
  return (typeof mapStateToProps === 'function')
    ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
    : undefined
}

export function whenMapStateToPropsIsMissing(mapStateToProps) {
  return (!mapStateToProps)
    ? wrapMapToPropsConstant(() => ({}))
    : undefined
}

export default [
  whenMapStateToPropsIsFunction,
  whenMapStateToPropsIsMissing
]

// match 依次调用这两个函数, 这里考虑了传入是函数以及是否传入时的情况。

同理看看mapDisaptachToProps.js,注意当我们没有传入mapDispatchToProps时的处理:

export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
  return (!mapDispatchToProps)
    ? wrapMapToPropsConstant(dispatch => ({ dispatch }))
    : undefined
}

也就是说,如果没有传入,则回返回一个dispatch供组件调用.

使用connect链接我们的组件之后,connectAdvanced会对我们传入的组件进行包裹操作,实际上是在内部搭建了一个connect组件,对于各种state变化,订阅发布的情况进行处理。

也就说,我们一旦使用了connect,返回的是一个经过包裹的组件(Countainer),这个组件内部的state更新,与store的交互,都已经在内部实现完毕。我们需要做的仅仅是定义mapStateToProps和mapDispatchToProps两个函数。

应用

我们一共有三个组件:

  1. AddTodo: 这个组件不存在state,所以不存在订阅,但是它核心功能是dispatch({type: 'ADD_TODO', id: nextTodoId
    ++, text: input.value }).

  2. VisibleTodoList: 这个组件向子组件提供了data: todos,也指派了behavior: dispatch({type: 'TOGGLE_TODO', id: ...}) ,所以我们需要定义mapStateToProps & mapDispatchToProps

  3. FilterLink: 这个组件自身具有一个属性 filter, 用来传递一个boolean给子组件,同时也指派behavior: dispatch({type: 'SET_VISIBILITY_FILTER, filter: props.filter}) , 所以也需要定义mapStateToProps & mapDispatchToProps .

我们不再需要手动定义这三个container了,除了AddTodo我们需要其包裹dispatch以外,其他的通通删(包括contextType)!然后使用connect对其子组件进行connect来创造container:

  • Connect AddTodo
let AddTodo = ({ dispatch }) => {  //change const to let
  let input,
      nextTodoId = 0
  return(
    <div>
      <input ref={node => {
        input = node
      }} />
      <button onClick={() => {
        dispatch({
          type: 'ADD_TODO',
          id: nextTodoId++,
          text: input.value
        })
        input.value = ''
      }}>
        Add Todo
      </button>
    </div>)
}
// It's pretty common pattern to inject just the dispatch function. 
// This is why if you specify null or any false value in connect 
// as the second argument, you're going to get dispatch injected as a prop. 

// The default behavior will be to not subscribe to this store 
// and to inject just the dispatch function as a prop.
AddTodo = connect()(AddTodo)
  • Connect TodoList
const mapStateToTodoListProps = (state) => {

  const getVisibleTodos = (todos, filter) => {
    switch (filter) {
      case 'SHOW_ALL':
        return todos
      case 'SHOW_COMPLETED':
        return todos.filter(t => t.completed)
      case 'SHOW_ACTIVE':
        return todos.filter(t => !t.completed)
    }
  }

  return {
    todos: getVisibleTodos(
      state.todos,
      state.visibilityFilter
    ),
  }
}

const mapDispatchToTodoListProps = (dispatch) => {
  return {
    onTodoClick: id => {
      dispatch({
        type: 'TOGGLE_TODO',
        id
      })
    }
  }
}

const VisibleTodoList = connect(
  mapStateToTodoListProps,
  mapDispatchToTodoListProps
)(TodoList)
  • Connect Link
const mapStateToLinkProps = (state, ownProps) => { 
  // Beacause FilterLink itself has a property 'filter'
  // It is fairly common to use the container props when calculating the child props,
  // so this is why props are passed as a second argument to mapStateToProps

  return {
    active: ownProps.filter === state.visibilityFilter
  }
}

const mapDispatchToLinkProps = (dispatch, ownProps) => {
  return {
    onClick: () => {
      dispatch({
        type: 'SET_VISIBILITY_FILTER',
        ownProps.filter
      })
    }
  }
}

const FilterLink = connect(
  mapStateToLinkProps,
  mapDispatchToLinkProps
)(Link)

Ok, 几乎完美, 大大减少了代码量。

我们还能做的更好吗?

观察dispatch action,type几乎是不变的,我们何不将它封装起来,这就是actionCreator的由来。

下一节我们进行这最后一步.

results matching ""

    No results matching ""