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)
}
}
未完待續~