基于源码版本号:v7.2.2
老版本的最简单实现
同样我们来看一下react-redux中常用的api;
核心API
- connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)
- mapStateToProps?: (state, ownProps?) => Object
- mapDispatchToProps?: Object | (dispatch, ownProps?) => Object
- mergeProps?: (stateProps, dispatchProps, ownProps) => Object
- options?: Object
- Provider Props参数:
- store
- children: element子节点
- context: 新的content,一般不传,内部存在默认ReactReduxContext
- hooks 方法
- useSelector(selector: Function, equalityFn?: Function)
- useDispatch()
- useStore()
前置
- react-redux实现的基础: react中的context,不懂的可以先去官网看下.
- 整个过程,我会按照 Subscription,Context,Provider,useReduxContext,useStore,useDispatch,useSelector,connect顺序,尽可能的讲清除整个订阅更新流程.
(备注:由于connect中逻辑代码太多,我放到最后讲)
一: Subscription
react-redux源码内部自己实现了一个发布订阅器
- Subscription代码
export default class Subscription {
constructor(store, parentSub) {
this.store = store // createStore创建的store对象
this.parentSub = parentSub // 这个参数很重要,很多时候传入Provider中的subscription
this.unsubscribe = null
this.listeners = nullListeners
this.handleChangeWrapper = this.handleChangeWrapper.bind(this)
}
/* 添加事件订阅 */
addNestedSub(listener) {
this.trySubscribe()
return this.listeners.subscribe(listener)
}
/* 订阅事件派发 */
notifyNestedSubs() {
this.listeners.notify()
}
handleChangeWrapper() {
if (this.onStateChange) {
this.onStateChange()
}
}
isSubscribed() {
return Boolean(this.unsubscribe)
}
/* 开始订阅 */
trySubscribe() {
if (!this.unsubscribe) {
/* 存在2中情况 */
/* 情况一:已经存在订阅器,这添加一个新的订阅 */
/* 情况二:不存在老的订阅器,这里往store中订阅个内容,Provider中就是这种情况 */
this.unsubscribe = this.parentSub
? this.parentSub.addNestedSub(this.handleChangeWrapper)
: this.store.subscribe(this.handleChangeWrapper)
/* 基于链表实现了一个listeners */
this.listeners = createListenerCollection()
}
}
/* 摧毁整个订阅 */
tryUnsubscribe() {
if (this.unsubscribe) {
this.unsubscribe()
this.unsubscribe = null
this.listeners.clear()
this.listeners = nullListeners
}
}
}
- createListenerCollection的实现看这里,避免篇幅太长,放在了其他路径下,不影响整体阅读。
二:Context
import React from 'react''
/* 创建默认的全局context */
export const ReactReduxContext = /*#__PURE__*/ React.createContext(null)
if (process.env.NODE_ENV !== 'production') {
ReactReduxContext.displayName = 'ReactRedux'
}
export default ReactReduxContext
新的context API通过React.createContext创建
三:Provider
import React, { useMemo, useEffect } from 'react'
import { ReactReduxContext } from './Context'
import Subscription from '../utils/Subscription'
function Provider({ store, context, children }) {
// 借助useMemo进行性能优化,使用上:依赖参数只进行浅比较
const contextValue = useMemo(() => {
/* 创建发布订阅器 */
const subscription = new Subscription(store)
subscription.onStateChange = subscription.notifyNestedSubs // 这是一个准备订阅到store中的内容
return {
store,
subscription,
}
}, [store]) // 基于store,正常情况下引用地址都不会变
const previousState = useMemo(() => store.getState(), [store])
/* previousState也是根据store,所以正常情况下,useEffect内容只进行一次执行 */
useEffect(() => {
const { subscription } = contextValue
subscription.trySubscribe()
// 对应Subscription 中this.store.subscribe(this.handleChangeWrapper)
// 将subscription.notifyNestedSubs订阅到store中
/* 首次会进行唯一一次执行,进行派发初始化 */
if (previousState !== store.getState()) {
subscription.notifyNestedSubs()
}
return () => {
subscription.tryUnsubscribe() // 摧毁订阅
subscription.onStateChange = null
}
}, [contextValue, previousState])
const Context = context || ReactReduxContext
return <Context.Provider value={contextValue}>{children}</Context.Provider>
}
export default Provider
上面代码中比较核心的点是:
- 通过Context.Provider,提供了一个全局context
- store,subscription通过context向下传递
- 将subscription.notifyNestedSubs订阅到了store中
这时候我们简单推测下,useSelector,connect中会做些什么?
- 拿到context中的 subscription, 通过addNestedSub往里面加事件(数据更新,组件render)
- store.dispatch时自然会执行subscription.notifyNestedSubs,更新放入的事件
看到这里,对react-redux整个流程应该有个简单了解,不然请回头再看下前面的代码。
四:useReduxContext
import { useContext } from 'react'
import { ReactReduxContext } from '../components/Context'
export function useReduxContext() {
/* 拿到contextValue */
const contextValue = useContext(ReactReduxContext)
return contextValue
}
hooks API通过useContext获取到context的值
五:useStore
import { useContext } from 'react'
import { ReactReduxContext } from '../components/Context'
import { useReduxContext as useDefaultReduxContext } from './useReduxContext'
/* createStoreHook允许传入额外定义的context, 正常情况下用默认的ReactReduxContext 就行 */
export function createStoreHook(context = ReactReduxContext) {
const useReduxContext =
context === ReactReduxContext
? useDefaultReduxContext
: () => useContext(context)
return function useStore() {
/* 从contextValue中拿到store */
const { store } = useReduxContext()
return store
}
}
export const useStore = /*#__PURE__*/ createStoreHook()
获取Provider中 contextValue中的store
六:useDispatch
import { ReactReduxContext } from '../components/Context'
import { useStore as useDefaultStore, createStoreHook } from './useStore'
/* createDispatchHook同一允许我们传入额外定义的context, 用默认的ReactReduxContext就行 */
export function createDispatchHook(context = ReactReduxContext) {
const useStore =
context === ReactReduxContext ? useDefaultStore : createStoreHook(context)
return function useDispatch() {
const store = useStore()
/* 拿到store中的dispatch */
return store.dispatch
}
}
export const useDispatch = /*#__PURE__*/ createDispatchHook()
从store中拿到dispatch
七:useSelector
- createSelectorHook代码
import { useReducer, useRef, useMemo, useContext, useDebugValue } from 'react'
import { useReduxContext as useDefaultReduxContext } from './useReduxContext'
import { ReactReduxContext } from '../components/Context'
/* 一样允许我们传入额外定义的context, 用默认的ReactReduxContext就行 */
export function createSelectorHook(context = ReactReduxContext) {
const useReduxContext =
context === ReactReduxContext
? useDefaultReduxContext
: () => useContext(context)
return function useSelector(selector, equalityFn = refEquality) {
const { store, subscription: contextSub } = useReduxContext()
const selectedState = useSelectorWithStoreAndSubscription(
selector,
equalityFn,
store,
contextSub
)
useDebugValue(selectedState)
return selectedState
}
}
export const useSelector = /*#__PURE__*/ createSelectorHook()
上面的操作和猜测的一样,从context中拿到subscription,并进行操作
- selector传参是什么
/* 定义的根据state计算,的function */
/* 例如 */
const todo = useSelector(state => state.todos[props.id])
- equalityFn传参是什么
/* 比较函数,满足什么条件不进行更新 */
const refEquality = (a, b) => a === b;// 默认值
- useIsomorphicLayoutEffect
import { useEffect, useLayoutEffect } from 'react'
export const useIsomorphicLayoutEffect =
typeof window !== 'undefined' &&
typeof window.document !== 'undefined' &&
typeof window.document.createElement !== 'undefined'
? useLayoutEffect
: useEffect
- useSelectorWithStoreAndSubscription代码
import { useReducer, useRef, useMemo, useContext, useDebugValue } from 'react'
import { useReduxContext as useDefaultReduxContext } from './useReduxContext'
import Subscription from '../utils/Subscription'
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
import { ReactReduxContext } from '../components/Context'
function useSelectorWithStoreAndSubscription(
selector,
equalityFn,
store,
contextSub
) {
// 视图更新,关键代码,模拟forceUpdate
const [, forceRender] = useReducer((s) => s + 1, 0)
// contentSub也就是Provider中的那个subscription
const subscription = useMemo(() => new Subscription(store, contextSub), [
store,
contextSub,
])
/* 缓存变量定义 */
const latestSubscriptionCallbackError = useRef()
const latestSelector = useRef()
const latestStoreState = useRef()
const latestSelectedState = useRef()
const storeState = store.getState()
let selectedState
try {
if (
selector !== latestSelector.current ||
storeState !== latestStoreState.current ||
latestSubscriptionCallbackError.current
) {
// 只在第一次时,进行计算,剩下的都使用缓存变量内容
selectedState = selector(storeState)
} else {
selectedState = latestSelectedState.current
}
} catch (err) {
if (latestSubscriptionCallbackError.current) {
err.message += `\nThe error may be correlated with this previous error:\n${latestSubscriptionCallbackError.current.stack}\n\n`
}
throw err
}
// 缓存对象
useIsomorphicLayoutEffect(() => { // 浏览器环境下就是useEffect
latestSelector.current = selector
latestStoreState.current = storeState
latestSelectedState.current = selectedState
latestSubscriptionCallbackError.current = undefined
})
useIsomorphicLayoutEffect(() => {
function checkForUpdates() {
try {
const newSelectedState = latestSelector.current(store.getState())
// 满足什么条件,不进行更新
if (equalityFn(newSelectedState, latestSelectedState.current)) {
return
}
latestSelectedState.current = newSelectedState
} catch (err) {
latestSubscriptionCallbackError.current = err
}
forceRender() // 视图更新
}
subscription.onStateChange = checkForUpdates
subscription.trySubscribe() // 往Provider中subscription中添加更新的内容
checkForUpdates()
return () => subscription.tryUnsubscribe()
}, [store, subscription])
return selectedState
}
更新的关键代码就在里面:
const [, forceRender] = useReducer((s) => s + 1, 0),视图更新的关键,
然后将整个checkForUpdates,通过addNestedSub加入到Provider的subscription,打通整个更新流程
八:connect
export function createConnect({
connectHOC = connectAdvanced,
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory,
} = {}) {
return 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(selectorFactory, {
methodName: 'connect',
getDisplayName: (name) => `Connect(${name})`,
shouldHandleStateChanges: Boolean(mapStateToProps),
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
pure,
areStatesEqual,
areOwnPropsEqual,
areStatePropsEqual,
areMergedPropsEqual,
...extraOptions,
})
}
}
export default /*#__PURE__*/ createConnect()
相信第一次看到代码的时候,应该是懵的,解释下,match,initMapStateToProps,initMapDispatchToProps, initMergeProps是对参数的处理
- 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
}.`
)
}
}
match配合
defaultMapStateToPropsFactories,
defaultMapDispatchToPropsFactories,
defaultMergePropsFactories,
进行参数处理,存在合理值就返回
mapDispatchToProps的对象形式处理就是在这一过程中借助bindActionCreators实现
我们再看看主流程中selectorFactory的实现。
- 这是selectorFactory中的源码
export default function finalPropsSelectorFactory(
dispatch,
{ initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }
) {
const mapStateToProps = initMapStateToProps(dispatch, options)
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
const mergeProps = initMergeProps(dispatch, options)
/* pure是一个优化参数,默认为true */
const selectorFactory = options.pure
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
)
}
上面涉及到了一个关键属性pure,
-
来个思考题: reducer书写时返回相同的state引用可以吗?
答案:pure改成false的话可以,但不建议这么做 -
impureFinalPropsSelectorFactory
/* 直接计算,不进行任何优化 */
export function impureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch
) {
return function impureFinalPropsSelector(state, ownProps) {
return mergeProps(
mapStateToProps(state, ownProps),
mapDispatchToProps(dispatch, ownProps),
ownProps
)
}
}
- pureFinalPropsSelectorFactory
/* 新老值进行比较,进行性能优化 */
export function pureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
{ areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
let hasRunAtLeastOnce = false
let state
let ownProps
let stateProps
let dispatchProps
let mergedProps
/* 第一次也是直接计算,但记录了初始值 */
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
}
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
}
function handleNewState() {
const nextStateProps = mapStateToProps(state, ownProps)
const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps) // shallowEqual方式比较
stateProps = nextStateProps
if (statePropsChanged)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
function handleSubsequentCalls(nextState, nextOwnProps) {
const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps) // shallowEqual方式比较
const stateChanged = !areStatesEqual(nextState, state) // === 严格比较
state = nextState
ownProps = nextOwnProps
if (propsChanged && stateChanged) return handleNewPropsAndNewState()
if (propsChanged) return handleNewProps()
if (stateChanged) return handleNewState()
return mergedProps
}
return function pureFinalPropsSelector(nextState, nextOwnProps) {
return hasRunAtLeastOnce
? handleSubsequentCalls(nextState, nextOwnProps)
: handleFirstCall(nextState, nextOwnProps)
}
}
代码中涉及到的比较方式
- strictEqual
function strictEqual(a, b) {
return a === b
}
- shallowEqual(包涵了对象的第一层比较)
function is(x, y) {
if (x === y) {
return x !== 0 || y !== 0 || 1 / x === 1 / y
} else {
return x !== x && y !== y
}
}
export default function shallowEqual(objA, objB) {
if (is(objA, objB)) return true
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false
}
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
if (keysA.length !== keysB.length) return false
for (let i = 0; i < keysA.length; i++) {
if (
!Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false
}
}
return true
}
上面可以看到,其实selectorFactory也是对参数的处理,没有设计到更新的核心操作,下面进入核心的connectHOC代码内,
- connectAdvanced
export default function connectAdvanced(
selectorFactory,
{
getDisplayName = (name) => `ConnectAdvanced(${name})`,
methodName = 'connectAdvanced',
renderCountProp = undefined,
shouldHandleStateChanges = true,
storeKey = 'store',
withRef = false,
forwardRef = false,
context = ReactReduxContext,
...connectOptions
} = {}
) {
const Context = context
return function wrapWithConnect(WrappedComponent) {
const wrappedComponentName =
WrappedComponent.displayName || WrappedComponent.name || 'Component'
const displayName = getDisplayName(wrappedComponentName)
const selectorFactoryOptions = {
...connectOptions,
getDisplayName,
methodName,
renderCountProp,
shouldHandleStateChanges,
storeKey,
displayName,
wrappedComponentName,
WrappedComponent,
}
const { pure } = connectOptions
function createChildSelector(store) {
return selectorFactory(store.dispatch, selectorFactoryOptions)
}
const usePureOnlyMemo = pure ? useMemo : (callback) => callback()
function ConnectFunction(props){
// ...
}
const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction
Connect.WrappedComponent = WrappedComponent
Connect.displayName = displayName
if (forwardRef) {
const forwarded = React.forwardRef(function forwardConnectRef(
props,
ref
) {
return <Connect {...props} reactReduxForwardedRef={ref} />
})
forwarded.displayName = displayName
forwarded.WrappedComponent = WrappedComponent
return hoistStatics(forwarded, WrappedComponent)
}
return hoistStatics(Connect, WrappedComponent)
}
}
可以看到返回的就是一个高阶组件,并forwardRef进行了判断,存在时,保证内部可访问到ref实例
hoistStatics 为工具库 hoist-non-react-statics,防止高阶组件静态属性丢失.
- 上面中的ConnectFunction 来看一下
function ConnectFunction(props) {
const [
propsContext,
reactReduxForwardedRef,
wrapperProps,
] = useMemo(() => {
const { reactReduxForwardedRef, ...wrapperProps } = props
return [props.context, reactReduxForwardedRef, wrapperProps]
}, [props])
/* 组件props中是否存在context,否则默认使用ReactReduxContext */
const ContextToUse = useMemo(() => {
return propsContext &&
propsContext.Consumer &&
isContextConsumer(<propsContext.Consumer />)
? propsContext
: Context
}, [propsContext, Context])
// 获取到Provider中的contextValue
const contextValue = useContext(ContextToUse)
const didStoreComeFromProps =
Boolean(props.store) &&
Boolean(props.store.getState) &&
Boolean(props.store.dispatch)
const didStoreComeFromContext =
Boolean(contextValue) && Boolean(contextValue.store)
const store = didStoreComeFromProps ? props.store : contextValue.store
const childPropsSelector = useMemo(() => {
return createChildSelector(store)
}, [store])
// Provider中的subscription及其事件发布
const [subscription, notifyNestedSubs] = useMemo(() => {
if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY
const subscription = new Subscription(
store,
didStoreComeFromProps ? null : contextValue.subscription
)
const notifyNestedSubs = subscription.notifyNestedSubs.bind(
subscription
)
return [subscription, notifyNestedSubs]
}, [store, didStoreComeFromProps, contextValue])
const overriddenContextValue = useMemo(() => {
if (didStoreComeFromProps) {
return contextValue
}
return {
...contextValue,
subscription,
}
}, [didStoreComeFromProps, contextValue, subscription])
/* 强制更新 */
const [
[previousStateUpdateResult],
forceComponentUpdateDispatch,
] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)
if (previousStateUpdateResult && previousStateUpdateResult.error) {
throw previousStateUpdateResult.error
}
const lastChildProps = useRef()
const lastWrapperProps = useRef(wrapperProps)
const childPropsFromStoreUpdate = useRef()
const renderIsScheduled = useRef(false)
const actualChildProps = usePureOnlyMemo(() => {
if (
childPropsFromStoreUpdate.current &&
wrapperProps === lastWrapperProps.current
) {
return childPropsFromStoreUpdate.current
}
return childPropsSelector(store.getState(), wrapperProps)
}, [store, previousStateUpdateResult, wrapperProps])
/*useIsomorphicLayoutEffectWithArgs同样理解成useEffect即可*/
useIsomorphicLayoutEffectWithArgs(captureWrapperProps, [
lastWrapperProps,
lastChildProps,
renderIsScheduled,
wrapperProps,
actualChildProps,
childPropsFromStoreUpdate,
notifyNestedSubs,
])
/*该过程包括了更新事件的订阅*/
useIsomorphicLayoutEffectWithArgs(
subscribeUpdates,
[
shouldHandleStateChanges,
store,
subscription,
childPropsSelector,
lastWrapperProps,
lastChildProps,
renderIsScheduled,
childPropsFromStoreUpdate,
notifyNestedSubs,
forceComponentUpdateDispatch,
],
[store, subscription, childPropsSelector]
)
const renderedWrappedComponent = useMemo(
() => (
<WrappedComponent
{...actualChildProps}
ref={reactReduxForwardedRef}
/>
),
[reactReduxForwardedRef, WrappedComponent, actualChildProps]
)
const renderedChild = useMemo(() => {
if (shouldHandleStateChanges) {
return (
<ContextToUse.Provider value={overriddenContextValue}>
{renderedWrappedComponent}
</ContextToUse.Provider>
)
}
return renderedWrappedComponent
}, [ContextToUse, renderedWrappedComponent, overriddenContextValue])
return renderedChild
}
- captureWrapperProps
function captureWrapperProps(
lastWrapperProps,
lastChildProps,
renderIsScheduled,
wrapperProps,
actualChildProps,
childPropsFromStoreUpdate,
notifyNestedSubs
) {
lastWrapperProps.current = wrapperProps
lastChildProps.current = actualChildProps
renderIsScheduled.current = false
// 首次更新触发
if (childPropsFromStoreUpdate.current) {
childPropsFromStoreUpdate.current = null
notifyNestedSubs()
}
}
- subscribeUpdates
function subscribeUpdates(
shouldHandleStateChanges,
store,
subscription,
childPropsSelector,
lastWrapperProps,
lastChildProps,
renderIsScheduled,
childPropsFromStoreUpdate,
notifyNestedSubs,
forceComponentUpdateDispatch
) {
if (!shouldHandleStateChanges) return
let didUnsubscribe = false
let lastThrownError = null
const checkForUpdates = () => {
if (didUnsubscribe) {
return
}
const latestStoreState = store.getState()
let newChildProps, error
try {
newChildProps = childPropsSelector(
latestStoreState,
lastWrapperProps.current
)
} catch (e) {
error = e
lastThrownError = e
}
if (!error) {
lastThrownError = null
}
if (newChildProps === lastChildProps.current) {
if (!renderIsScheduled.current) {
notifyNestedSubs()
}
} else {
lastChildProps.current = newChildProps
childPropsFromStoreUpdate.current = newChildProps
renderIsScheduled.current = true
forceComponentUpdateDispatch({
type: 'STORE_UPDATED',
payload: {
error,
},
})
}
}
// useSelector相同的套路
subscription.onStateChange = checkForUpdates
subscription.trySubscribe()
checkForUpdates()
const unsubscribeWrapper = () => {
didUnsubscribe = true
subscription.tryUnsubscribe()
subscription.onStateChange = null
if (lastThrownError) {
throw lastThrownError
}
}
return unsubscribeWrapper
}
上面过程中,使用了和useSelector相同的套路,通过forceComponentUpdateDispatch并订阅来实现更新过程,整个核心更新流程已经写完了。