react-redux connect源码理解
-
- connect函数源码
connect函数源码
// match函数基本技术介绍 (dispatch, options) => (nextState, nextOwnProps) => nextFinalProps
function connect(
mapStateToProps,
mapDispatchToProps,
mergeProps,
{
pure = true,
areStatesEqual = strictEqual,
areOwnPropsEqual = shallowEqual,
areStatePropsEqual = shallowEqual,
areMergedPropsEqual = shallowEqual,
...extraOptions
} = {}
) {
const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps')
const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
return connectHOC(...)
}
可以看到的是在connect函数中初始化处理了入参mapStateToProps,mapDispatchToProps,mergeProps,让他们符合初始化函数的形式
- 初始化函数形式
(dispatch, options) => (nextState, nextOwnProps) => nextFinalProps
处理入参之后就返回了一个React 高阶组件(HOC)
connectHOC
- connectHOC的默认入参有
- connectHOC默认值connectAdvanced函数的代码
import hoistStatics from 'hoist-non-react-statics' // 复制子组件信息到父组件
import invariant from 'invariant' // 输出错误信息
import { Component, createElement } from 'react'
import { isValidElementType } from 'react-is' // 判断是否是react元素的方法
import Subscription from '../utils/Subscription' // 实现发布订阅的类函数
import { storeShape, subscriptionShape } from '../utils/PropTypes' // store的类型和subscription的类型
let hotReloadingVersion = 0 // 重载版本
const dummyState = {} // 假的state?
function noop() {} // 默认函数
// 通过store和合并之后的selector计算得到最终的props
function makeSelectorStateful(sourceSelector, store) {
// wrap the selector in an object that tracks its results between runs.
const selector = {
run: function runComponentSelector(props) {
try {
const nextProps = sourceSelector(store.getState(), props)
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
}
export default function connectAdvanced(
selectorFactory, // 合并connect selector参数(mapState,mapDispath,mergeProps)的一个方法
{
getDisplayName = name => `ConnectAdvanced(${name})`,
methodName = 'connectAdvanced',
renderCountProp = undefined, // 渲染技术
shouldHandleStateChanges = true, // 状态改变时是否更新
// the key of props/context to get the store
storeKey = 'store', // 关键key: store, 用于连接redux
// if true, the wrapped element is exposed by this HOC via the getWrappedInstance() function.
withRef = false,
...connectOptions
} = {}
) {
const subscriptionKey = storeKey + 'Subscription'
const version = hotReloadingVersion++
const contextTypes = {
[storeKey]: storeShape,
[subscriptionKey]: subscriptionShape,
}
const childContextTypes = {
[subscriptionKey]: subscriptionShape,
}
return function wrapWithConnect(WrappedComponent) {
invariant(
isValidElementType(WrappedComponent),
`You must pass a component to the function returned by ` +
`${methodName}. Instead received ${JSON.stringify(WrappedComponent)}`
)
const wrappedComponentName = WrappedComponent.displayName
|| WrappedComponent.name
|| 'Component'
const displayName = getDisplayName(wrappedComponentName)
const selectorFactoryOptions = {
...connectOptions,
getDisplayName,
methodName,
renderCountProp,
shouldHandleStateChanges,
storeKey,
withRef,
displayName,
wrappedComponentName,
WrappedComponent
}
class Connect extends Component {
constructor(props, context) {
super(props, context)
this.version = version // 版本?重载的次数记录?
this.state = {}
this.renderCount = 0 // 渲染次数
// 获取redux store,优先从父组件获取。正常情况下,connect的组件最好不要传key为store的props
this.store = props[storeKey] || context[storeKey]
// 判断是不是父组件就有store,是不是父组件模式
this.propsMode = Boolean(props[storeKey])
this.setWrappedInstance = this.setWrappedInstance.bind(this)
invariant(this.store,
`Could not find "${storeKey}" in either the context or props of ` +
`"${displayName}". Either wrap the root component in a <Provider>, ` +
`or explicitly pass "${storeKey}" as a prop to "${displayName}".`
)
// 初始化selector
this.initSelector()
// 初始化发布订阅的模型
this.initSubscription()
}
// 声明React树节点下方组件可获取的context
getChildContext() {
const subscription = this.propsMode ? null : this.subscription
return {
[subscriptionKey]: subscription || this.context[subscriptionKey]
}
}
initSelector() {
// 初始selector, 这里绑定了this.store.dispatch函数和mapStateToProps三个函数的关系
const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
this.selector = makeSelectorStateful(sourceSelector, this.store)
// 初始化执行
this.selector.run(this.props)
}
initSubscription() {
// 更新不需要订阅就不
if (!shouldHandleStateChanges) return
// 获取发布订阅的方法对象,可以看redux或的具体实现
const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey];
// this.onStateChange订阅store的变化
this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
// 触发更新propsmode为true时候的更新函数?
this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
}
// 订阅store更新函数
onStateChange() {
this.selector.run(this.props)
if (!this.selector.shouldComponentUpdate) {
this.notifyNestedSubs()
} else {
this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
this.setState(dummyState)
}
}
// 组件创建后更新props相关
componentDidMount() {
if (!shouldHandleStateChanges) return
this.subscription.trySubscribe()
this.selector.run(this.props)
if (this.selector.shouldComponentUpdate) this.forceUpdate()
}
// 接受新的props更新
componentWillReceiveProps(nextProps) {
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
}
getWrappedInstance() {
invariant(withRef,
`To access the wrapped instance, you need to specify ` +
`{ withRef: true } in the options argument of the ${methodName}() call.`
)
return this.wrappedInstance
}
setWrappedInstance(ref) {
this.wrappedInstance = ref
}
notifyNestedSubsOnComponentDidUpdate() {
this.componentDidUpdate = undefined
this.notifyNestedSubs()
}
isSubscribed() {
return Boolean(this.subscription) && this.subscription.isSubscribed()
}
// 一些特殊情况处理,withRef, renderCountProp,this.propsMode为true并。。。
addExtraProps(props) {
if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props
const withExtras = { ...props }
if (withRef) withExtras.ref = this.setWrappedInstance
if (renderCountProp) withExtras[renderCountProp] = this.renderCount++
if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription
return withExtras
}
// 渲染函数
render() {
const selector = this.selector
selector.shouldComponentUpdate = false
if (selector.error) {
throw selector.error
} else {
return createElement(WrappedComponent, this.addExtraProps(selector.props))
}
}
}
/* eslint-enable react/no-deprecated */
Connect.WrappedComponent = WrappedComponent
Connect.displayName = displayName
Connect.childContextTypes = childContextTypes
Connect.contextTypes = contextTypes
Connect.propTypes = contextTypes
if (process.env.NODE_ENV !== 'production') {
Connect.prototype.componentWillUpdate = function componentWillUpdate() {
// We are hot reloading!
if (this.version !== version) {
this.version = version
this.initSelector()
let oldListeners = [];
if (this.subscription) {
oldListeners = this.subscription.listeners.get()
this.subscription.tryUnsubscribe()
}
this.initSubscription()
if (shouldHandleStateChanges) {
this.subscription.trySubscribe()
oldListeners.forEach(listener => this.subscription.listeners.subscribe(listener))
}
}
}
}
return hoistStatics(Connect, WrappedComponent)
}
}
未完待续~