主要的核心在于connect,下面通过一个简化版的connect来说下它的主要作用。
在第一篇文章的时候说过,connect这个函数其实最终会返回一个包着渲染组件的高阶组件。
它的基础作用如下:
1、从context里获取store
2、在componentWillMount 里通过mapStateToProps获取stateProp的值
3、在componentWillMount 里通过mapDispatchToProps获取dispatchProps的值
4、在componentWillMount 里订阅store的变化
5、将获得的stateProp,dispatchProps,还有自身的props合成一个props传给下面的组件
export const connect = (mapStateToProps,mapDispatchToProps) => (WrappedComponent) => { class Connect extends Component { static contextTypes = { store: PropTypes.object } constructor () { super() this.state = { allProps: {} } } componentWillMount () { const { store } = this.context this._updateProps() store.subscribe(() => this._updateProps()) } _updateProps () { const { store } = this.context let stateProps = mapStateToProps ? mapStateToProps(store.getState(),this.props) : {} // 防止 mapStateToProps 没有传入 let dispatchProps = mapDispatchToProps ? mapDispatchToProps(store.dispatch,this.props) : {} // 防止 mapDispatchToProps 没有传入 this.setState({ allProps: { ...stateProps,...dispatchProps,...this.props } }) } render () { return <WrappedComponent {...this.state.allProps} /> } } return Connect }
明白了上面之后,我们就可以来看真的connect长啥样了。
connect接受四个参数:mapStateToProps,mapDispatchToProps,mergeProps,optipons
返回:一个注入了 state 和 action creator 的 React 组件
具体的mapStateToProps,optipons如何使用可以看官方文档,
这里简单的说下~
mapStateToProps
传入:state,ownProps
输出:stateProps
这个非常关键,如果定义了这个参数,就会监听redux store的变化,没有的话,就不会。该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。
同时,如果指定了第二个ownProps,这个参数的值为传入到组件的props,只要组件接受到新的props,mapStateToProps也会被调用
mergeProps(function)
stateProps,dispatchProps,自身的props将传入到这个函数中。
默认是Object.assign({},ownProps,stateProps,dispatchProps)
options(Object)
pure = true ; // 默认值,这时候connector 将执行 shouldComponentUpdate 并且浅对比 mergeProps 的结果,避免不必要的更新。所以其实connect是有帮我们做性能优化的。
最终使用:
connect(mapStateToProps,mergeProps)(MyComponent)
啰嗦了那么多,看看Connect的源码的目录结构
connect.js
connect.js 就是将相关的参数传给connectAdvanced.js
其中有个参数:
// if mapStateToProps is falsy,the Connect component doesn't subscribe to store state changes // 所以,如果mapStateToProps没有传的话,是不订阅store更新的 shouldHandleStateChanges: Boolean(mapStateToProps),
connectAdvanced
重点看看connectAdvanced
其中有个比较重要的概念,selectorFactory 它的作用是负责运行选择器函数,这些函数从state,props,dispatch里计算出新的props。
例如:
export default connectAdvanced((dispatch,options) => (state,props) => ({ thing: state.things[props.thingId],saveThing: fields => dispatch(actionCreators.saveThing(props.thingId,fields)),}))(YourComponent)
selectorFactory
当pure为true的时候,返回的selector就会缓存它们各自的结果,这样connectAdvance里的shouldComponentUpdate就可以对比this.props和nextProps,当没有发生改变的时候返回false
不更新。当pure为false的时候,shouldComponentUpdate在任何时候都会返回true。
const selectorFactory = options.pure ? pureFinalPropsSelectorFactory : impureFinalPropsSelectorFactory
impureFinalPropsSelectorFactory
由于impure每次都会返回新的object,所以每一次shouldComponentUpdate对比的时候都一定会返回true
export function impureFinalPropsSelectorFactory( mapStateToProps,dispatch ) { return function impureFinalPropsSelector(state,ownProps) { return mergeProps( mapStateToProps(state,ownProps),mapDispatchToProps(dispatch,ownProps ) } }
pureFinalPropsSelectorFactory
而pureFinalPropsSelectorFactory 会缓存上一次stateProps,当需要更新的时候,当那哪一部分需要更新的时候才更新
export function pureFinalPropsSelectorFactory( mapStateToProps,dispatch,{ areStatesEqual,areOwnPropsEqual,areStatePropsEqual } ) { let hasRunAtLeastOnce = false let state let ownProps let stateProps let dispatchProps let mergedProps // 第一次跑的时候,就去获得mergedProps,并且将hasRunAtLeastOnce 设置成true, 下次跑的时候,就直接走handleSubsequentCalls function handleFirstCall(firstState,firstOwnProps) { state = firstState ownProps = firstOwnProps stateProps = mapStateToProps(state,ownProps) dispatchProps = mapDispatchToProps(dispatch,ownProps) mergedProps = mergeProps(stateProps,ownProps) hasRunAtLeastOnce = true return mergedProps } // 如果props state都变化的时候,mapStateToProps是一定会变化的, // 而mapDispatchToProps会看ownProps是否存在 function handleNewPropsAndNewState() { stateProps = mapStateToProps(state,ownProps) if (mapDispatchToProps.dependsOnOwnProps) dispatchProps = mapDispatchToProps(dispatch,ownProps) mergedProps = mergeProps(stateProps,ownProps) return mergedProps } function handleNewProps() { if (mapStateToProps.dependsOnOwnProps) stateProps = mapStateToProps(state,ownProps) return mergedProps } // 如果只是state改变的话,就只需要看mapStateToProps,然后他们还会比较前后 // 是否相等,如果不相等,就合并 function handleNewState() { const nextStateProps = mapStateToProps(state,ownProps) const statePropsChanged = !areStatePropsEqual(nextStateProps,stateProps) stateProps = nextStateProps if (statePropsChanged) mergedProps = mergeProps(stateProps,ownProps) return mergedProps } // 不是第二次的时候,走这个函数 function handleSubsequentCalls(nextState,nextOwnProps) { // 首先对比nextOwnProps跟ownProps是否相等 const propsChanged = !areOwnPropsEqual(nextOwnProps,ownProps) // 对比nextState,state是否相等 const stateChanged = !areStatesEqual(nextState,state) // 比较完成赋值,下一次对比时候使用 state = nextState ownProps = nextOwnProps // 当props跟state都变化的时候,看handleNewPropsAndNewState if (propsChanged && stateChanged) return handleNewPropsAndNewState() if (propsChanged) return handleNewProps() if (stateChanged) return handleNewState() // 注意 如果state跟props都没有发生改变的话,直接返回之前的mergedProps,// 组件不重新渲染 return mergedProps } return function pureFinalPropsSelector(nextState,nextOwnProps) { return hasRunAtLeastOnce ? handleSubsequentCalls(nextState,nextOwnProps) : handleFirstCall(nextState,nextOwnProps) } }
总结来说,当state变化的时候,mapStateToProps一定会被调用,而props变化的时候要看dependsOnOwnProps,计算出来之后,用mergProps更新。
更新之后的props在哪里进行比较呢?
上面说的,其实都依赖于connect里面的代码帮忙,connect在contructor里初始化selector
initSelector() { const sourceSelector = selectorFactory(this.store.dispatch,selectorFactoryOptions) this.selector = makeSelectorStateful(sourceSelector,this.store) this.selector.run(this.props) } function makeSelectorStateful(sourceSelector,store) { // wrap the selector in an object that tracks its results between runs. const selector = { // run其实做了两个事情 //1、去计算nextProps的值 //2、如果有更新,设置shouldComponentUpdate=true 跟 props run: function runComponentSelector(props) { try { const nextProps = sourceSelector(store.getState(),props) // 注意这里只是进行了一个浅比较,当不等的时候,shouldComponentUpdate = true if (nextProps !== selector.props || selector.error) { selector.shouldComponentUpdate = true selector.props = nextProps selector.error = null } } catch (error) { selector.shouldComponentUpdate = true selector.error = error } } } return selector }
有两个东西比较重要,一个是selector,还有一个是subscription
接下来 生命周期的话还是看react-redux里的写法吧~
componentDidMount() { //shouldHandleStateChanges 默认为true if (!shouldHandleStateChanges) return // componentWillMount fires during server side rendering,but componentDidMount and // componentWillUnmount do not. Because of this,trySubscribe happens during ...didMount. // Otherwise,unsubscription would never take place during SSR,causing a memory leak. // To handle the case where a child component may have triggered a state change by // dispatching an action in its componentWillMount,we have to re-run the select and maybe // re-render. // 意思就是SSR的时候会触发componentWillMount ,但是不会触发componentDidMount , // componentWillUnmount,所以订阅事件要放在componentDidMount里面,否则, 就没办法取消订阅 // 造成内存泄漏。 this.subscription.trySubscribe() this.selector.run(this.props) // 当shouldComponentUpdate= true的时候,强制刷新 if (this.selector.shouldComponentUpdate) this.forceUpdate() } componentWillReceiveProps(nextProps) { // 前面说过,this.selector.run有两点作用: // 1 计算下一个props // 2 通过对比看看shouldComponentUpdate的值 this.selector.run(nextProps) } shouldComponentUpdate() { return this.selector.shouldComponentUpdate } componentWillUnmount() { // 取消订阅,让所有值都复原 if (this.subscription) this.subscription.tryUnsubscribe() this.subscription = null this.notifyNestedSubs = noop this.store = null this.selector.run = noop this.selector.shouldComponentUpdate = false }
最关键的是,onStateChange,当我们订阅了这个函数的时候,每一次dispatch都会触发它,
onStateChange() { // 依旧是先通过selector获取nextProps的值 this.selector.run(this.props) // 然后如果shouldComponentUpdate=true if (!this.selector.shouldComponentUpdate) { // 这个是啥意思?是这样的,每个Connect组件里面都有一个subscription对象,它也是一个订阅模型 // 每个父的Connect订阅的是 子Connect组件的onStateChange函数,而父的Connect的onStateChange // 函数,被谁订阅呢?当然是store(redux)啦, 即流程为 // dispathc(action)---触发store的订阅即父的onStateChange---父的onStateChange触发即触发子 // Connect的onStateChange this.notifyNestedSubs() } else { this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate this.setState(dummyState) } }
最后在render的时候
render() { const selector = this.selector selector.shouldComponentUpdate = false if (selector.error) { throw selector.error } else { // 将mege的props传给wrappedComponent return createElement(WrappedComponent,this.addExtraProps(selector.props)) } }
最终
return hoistStatics(Connect,WrappedComponent)
想想hoistStatics是什么作用,它实际上就是类似Object.assign将子组件中的 static 方法复制进父组件,但不会覆盖组件中的关键字方法(如 componentDidMount)。
好长啊我都有点累了。
说了那么多,其实只要记住亮点就可以了:
1、connect是一个函数,会返回一个connect的类,里面包着我们要渲染的wrappedComponent,然后将stateProps,还有ownProps合并起来,一起传给wrappedComponent
2、connect其实帮我们做了性能的优化的,当state跟props发生改变时,selector如果变化,最终计算出来的结果会进行一次浅比较来设置shouldComponentUpdate防止重复渲染
参考:
1、https://segmentfault.com/a/11...
2、https://github.com/reactjs/re...