本文作者:IMWeb 黄qiong 原文出处:IMWeb社区 未经同意,禁止转载
主要的核心在于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,mapDispatchToProps,mergeProps,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是有帮我们做性能优化的。
最终使用:
代码语言:javascript复制connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyComponent)
在讲解connect之前先用一张流程图说明一下它的思路:
connect.js
connect.js 就是将相关的参数传给connectAdvanced.js
其中有个参数:
代码语言:javascript复制// 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。
例如:
代码语言:javascript复制 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。
代码语言:javascript复制 const selectorFactory = options.pure
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory
impureFinalPropsSelectorFactory
由于impure每次都会返回新的object,所以每一次shouldComponentUpdate对比的时候都一定会返回true
代码语言:javascript复制export function impureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch
) {
return function impureFinalPropsSelector(state, ownProps) {
return mergeProps(
mapStateToProps(state, ownProps),
mapDispatchToProps(dispatch, ownProps),
ownProps
)
}
}
pureFinalPropsSelectorFactory
而pureFinalPropsSelectorFactory 会缓存上一次stateProps, dispatchProps,mergeProps,当需要更新的时候,哪一部分才更新
代码语言:javascript复制export function pureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
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, dispatchProps, 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, 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
}
// 如果只是state改变的话,就只需要看mapStateToProps,然后他们还会比较前后
// 是否相等,如果不相等,就合并
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) {
// 首先对比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
代码语言:javascript复制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
}
接下来,生命周期的话还是看react-redux里的写法吧~
代码语言:javascript复制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都会触发它,
代码语言:javascript复制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的时候
代码语言:javascript复制 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))
}
}
最终
代码语言:javascript复制return hoistStatics(Connect, WrappedComponent)
想想hoistStatics是什么作用,它实际上就是类似Object.assign将子组件中的 static 方法复制进父组件,但不会覆盖组件中的关键字方法(如 componentDidMount)。
好长啊我都有点累了。
说了那么多,其实只要记住两点就可以了:
1、connect是一个函数,会返回一个connect的类,里面包着我们要渲染的wrappedComponent,然后将stateProps,dispatchProps,还有ownProps合并起来,一起传给wrappedComponent
2、connect其实帮我们做了性能的优化的,当state跟props发生改变时,selector如果变化,最终计算出来的结果会进行一次浅比较来设置shouldComponentUpdate防止重复渲染。
流程如下:
参考:
1、https://segmentfault.com/a/1190000006947061#articleHeader2
2、https://github.com/reactjs/react-redux/blob/master/src/utils/Subscription.js