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两个函数。
应用
我们一共有三个组件:
AddTodo: 这个组件不存在state,所以不存在订阅,但是它核心功能是
dispatch({type: 'ADD_TODO', id: nextTodoId
++, text: input.value })
.VisibleTodoList: 这个组件向子组件提供了data: todos,也指派了behavior:
dispatch({type: 'TOGGLE_TODO', id: ...})
,所以我们需要定义mapStateToProps
&mapDispatchToProps
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的由来。
下一节我们进行这最后一步.