继 Redux 源码学习以后,咱们来看一看 React Redux 是如何将 Redux 和 React 组合起来的。 我正在阅读的则是
7.2.0
版本代码。javascript
咱们依然围绕着 src
进行学习。能够看到源码中已经在使用 Hook 代码。若是你暂时还对 Hook 不是很了解的话,建议先前往 React 官网学习 Hook 内容。java
从目录来看 utils
依然是简单的工具, hooks
下则是一些对外提供的 Hook , connect
下则是咱们每次都要使用的高阶组件函数, components
则也是咱们每次都要使用的 Provider
组件。其中, connect/connect.js
实际上是对 components/connectAdvanced.js
的封装。react
示例代码中我会移除并省略掉一些逻辑代码以及一些我认为不那么重要的代码,即只展示我想要说明的内容。数组
有必要提到的是 utils/batch.js
和 index.js
中的部分源码,其中涉及到 React 协调中的批量更新,而且在 utils/Subscription.js
有使用到。缓存
// utils/batch.js let batch = callback => callback() export const setBatch = newBatch => (batch = newBatch) export const getBatch = () => batch // index.js import { setBatch } from './utils/batch' import { unstable_batchedUpdates as batch } from 'react-dom' setBatch(batch) 复制代码
从上面咱们能够看出 React Redux 这里使用的更新模式并非“普通”的执行函数,而是依赖于 React DOM 的批量更新,做为了解学习便可,能够增进一些本身的思路。如今,让咱们来看到 utils/Subscription.js
的源码:性能优化
function createListenerCollection() { // 依赖于 React DOM 的批量更新 const batch = getBatch() // 监听器链表头和尾 let first = null let last = null // 典型的闭包结构 return { // 清空链表 clear() { first = null last = null }, // 通知函数 notify() { // 调用依赖于 React DOM 的批量更新 batch(() => { let listener = first while (listener) { listener.callback() listener = listener.next } }) }, // 获取监听器列表 get() { let listeners = [] let listener = first while (listener) { listeners.push(listener) listener = listener.next } return listeners }, // 订阅回调函数,添加至监听器链表 subscribe(callback) { let isSubscribed = true let listener = (last = { callback, next: null, prev: last }) if (listener.prev) { listener.prev.next = listener } else { first = listener } // 返回取消订阅函数(典型的闭包结构) return function unsubscribe() { if (!isSubscribed || first === null) return isSubscribed = false // 将加入的监听器移除 if (listener.next) { listener.next.prev = listener.prev } else { last = listener.prev } if (listener.prev) { listener.prev.next = listener.next } else { first = listener.next } } } } } export default class Subscription { constructor(store, parentSub) { // 初始化 this.store = store this.parentSub = parentSub 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() } // 处理指定 API onStateChange handleChangeWrapper() { if (this.onStateChange) { this.onStateChange() } } // 是否尝试过订阅 isSubscribed() { return Boolean(this.unsubscribe) } // 订阅 trySubscribe() { if (!this.unsubscribe) { this.unsubscribe = this.parentSub ? this.parentSub.addNestedSub(this.handleChangeWrapper) : this.store.subscribe(this.handleChangeWrapper) // 建立监听器集合 this.listeners = createListenerCollection() } } // 退订清除缓存 tryUnsubscribe() { if (this.unsubscribe) { this.unsubscribe() this.unsubscribe = null this.listeners.clear() this.listeners = nullListeners } } } 复制代码
上面是一个订阅器的简单实现,也是 Provider
组件触发更新的原理,那么话很少说,咱们来看到 components
目录下 Provider.js
的相关源码:markdown
const ReactReduxContext = /*#__PURE__*/ React.createContext(null) function Provider({ store, context, children }) { const contextValue = useMemo(() => { // 实例化 Subscription const subscription = new Subscription(store) // API onStateChange 设置 subscription.onStateChange = subscription.notifyNestedSubs return { store, subscription } }, [store]) // Redux state 获取 API 调用 const previousState = useMemo(() => store.getState(), [store]) useEffect(() => { const { subscription } = contextValue // 尝试订阅并建立监听器集合 subscription.trySubscribe() if (previousState !== store.getState()) { // 若新旧 state 不同则发起通知,执行监听器列表 subscription.notifyNestedSubs() } return () => { // 清除反作用 subscription.tryUnsubscribe() subscription.onStateChange = null } }, [contextValue, previousState]) // 用户自定义 Context 优先 const Context = context || ReactReduxContext // contextValue 包含整个 Redux 实例和订阅器 return <Context.Provider value={contextValue}>{children}</Context.Provider> } 复制代码
看到这里其实已经明白 Provider
组件的整个逻辑,即依据 store
或 previousState
的变化触发订阅退订的生命周期以及通知更新操做。那么再和 connect
高阶组件函数配合实现 React Redux 的功能。显然,到了这里我想你们都应该能猜到, Provider
组件用于注入 Redux 并控制逻辑,connect
高阶组件函数来处理用户想要的 store
以 props
的形式传入。咱们先来看到 connectAdvanced.js
的源码吧(下面内容有些多,我陪你一点点看下去):闭包
export default function connectAdvanced(
/*
选择器工厂函数(默认官方提供)做用是生成相似这样的代码内容:
export default connectAdvanced((dispatch, options) => (state, props) => ({
thing: state.things[props.thingId],
saveThing: fields => dispatch(actionCreators.saveThing(props.thingId, fields)),
}))(YourComponent)
*/
selectorFactory,
// 配置参数,官方已在 connect 进行封装(固然用户能够本身魔改),因此咱们在使用的时候用的是 connect
{
// the func used to compute this HOC's displayName from the wrapped component's displayName.
// probably overridden by wrapper functions such as connect()
getDisplayName = name => `ConnectAdvanced(${name})`,
// shown in error messages
// probably overridden by wrapper functions such as connect()
methodName = 'connectAdvanced',
// REMOVED: if defined, the name of the property passed to the wrapped element indicating the number of
// calls to render. useful for watching in react devtools for unnecessary re-renders.
renderCountProp = undefined,
// determines whether this HOC subscribes to store changes
shouldHandleStateChanges = true,
// REMOVED: the key of props/context to get the store
storeKey = 'store',
// REMOVED: expose the wrapped component via refs
withRef = false,
// use React's forwardRef to expose a ref of the wrapped component
forwardRef = false,
// the context consumer to use
context = ReactReduxContext,
// additional options are passed through to the selectorFactory
...connectOptions
} = {}
) {
const Context = context
// 典型的 HOC 写法
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
}
// “ prue ”即一种模式,默认为开启。影响 Memo 相关内容,能够视作是一种性能优化
const { pure } = connectOptions
const usePureOnlyMemo = pure ? useMemo : callback => callback()
// 函数组件 ConnectFunction
function ConnectFunction(props) {
// props 分类
const [propsContext, forwardedRef, wrapperProps] = useMemo(() => {
const { forwardedRef, ...wrapperProps } = props
return [props.context, forwardedRef, wrapperProps]
}, [props])
// 选择默认 Context 或用户本身传入的 Context
const ContextToUse = useMemo(() => {
return propsContext &&
propsContext.Consumer &&
isContextConsumer(<propsContext.Consumer />)
? propsContext
: Context
}, [propsContext, Context])
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
// props 选择器,对应源码为 finalPropsSelectorFactory 函数,能够理解为一个包装层
const childPropsSelector = useMemo(() => {
return selectorFactory(store.dispatch, selectorFactoryOptions)/* createChildSelector(store) */
}, [store])
// 订阅器及通知函数建立
const [subscription, notifyNestedSubs] = useMemo(() => {
if (!shouldHandleStateChanges) return [null, null]/* NO_SUBSCRIPTION_ARRAY */
// 实例化 Subscription
const subscription = new Subscription(
store,
didStoreComeFromProps ? null : contextValue.subscription
)
// 绑定 notifyNestedSubs 的执行上下文环境
const notifyNestedSubs = subscription.notifyNestedSubs.bind(
subscription
)
return [subscription, notifyNestedSubs]
}, [store, didStoreComeFromProps, contextValue])
// Determine what {store, subscription} value should be put into nested context, if necessary,
// and memoize that value to avoid unnecessary context updates.
const overriddenContextValue = useMemo(() => {
if (didStoreComeFromProps) {
return contextValue
}
return {
...contextValue,
subscription
}
}, [didStoreComeFromProps, contextValue, subscription])
const lastChildProps = useRef()
const lastWrapperProps = useRef(wrapperProps)
const childPropsFromStoreUpdate = useRef()
const renderIsScheduled = useRef(false)
// 实际的 props
const actualChildProps = usePureOnlyMemo(() => {
if (
childPropsFromStoreUpdate.current &&
wrapperProps === lastWrapperProps.current
) {
return childPropsFromStoreUpdate.current
}
// props 选择器,同包装层
return childPropsSelector(store.getState(), wrapperProps)
}, [store, wrapperProps])
// Hook useEffect 或 useLayoutEffect
useIsomorphicLayoutEffect(/* captureWrapperProps */() => {
// 用于更新最新的 props 相关
lastWrapperProps.current = wrapperProps
lastChildProps.current = actualChildProps
renderIsScheduled.current = false
// 根据条件通知
if (childPropsFromStoreUpdate.current) {
childPropsFromStoreUpdate.current = null
notifyNestedSubs()
}
})
// Our re-subscribe logic only runs when the store/subscription setup changes
useIsomorphicLayoutEffect(/* subscribeUpdates */ () => {
if (!shouldHandleStateChanges) return
let didUnsubscribe = false
// 检查更新函数
const checkForUpdates = () => {
if (didUnsubscribe) { return }
const latestStoreState = store.getState()
// props 选择器(非包装层,能够理解为 impureFinalPropsSelector 函数,由于省略源码的关系,重名)
let newChildProps = childPropsSelector(
latestStoreState,
lastWrapperProps.current
)
// 若是 props 未变化,则此处无事可作 - 级联订阅更新
if (newChildProps === lastChildProps.current) {
if (!renderIsScheduled.current) {
// 通知
notifyNestedSubs()
}
} else {
// props 变化,触发通知
lastChildProps.current = newChildProps
childPropsFromStoreUpdate.current = newChildProps
renderIsScheduled.current = true
}
}
// 订阅 checkForUpdates
subscription.onStateChange = checkForUpdates
subscription.trySubscribe()
// 首次检查
checkForUpdates()
// 取消订阅函数用于返回
const unsubscribeWrapper = () => {
didUnsubscribe = true
subscription.tryUnsubscribe()
subscription.onStateChange = null
}
return unsubscribeWrapper
}, [store, subscription, childPropsSelector/* 包装层 */])
const renderedWrappedComponent = useMemo(
() => <WrappedComponent {...actualChildProps} ref={forwardedRef} />,
[forwardedRef, WrappedComponent, actualChildProps]
)
const renderedChild = useMemo(() => {
// 性能优化标示,若为 false 则不从新渲染
if (shouldHandleStateChanges) {
return (
<ContextToUse.Provider value={overriddenContextValue}>
{renderedWrappedComponent}
</ContextToUse.Provider>
)
}
return renderedWrappedComponent
}, [ContextToUse, renderedWrappedComponent, overriddenContextValue])
return renderedChild
}
const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction
Connect.WrappedComponent = WrappedComponent
Connect.displayName = displayName
// 此处省略含有 React.forwardRef 相关的特殊场景下的代码内容,若感兴趣请移步至 Github 仓库查看
// 关于 hoistStatics 请查看: Copies non-react specific statics from a child component to a parent component.
// Similar to Object.assign, but with React static keywords blacklisted from being overridden.
return hoistStatics(Connect, WrappedComponent)
}
}
复制代码
到这里, connectAdvanced
高阶组件函数的内容已阅读完,由于篇幅关系,我移除了部分代码,可能会致使理解不足,就像我在前面说的,这个高阶组件的功能就是如此,若你们想深刻建议前往 Github 仓库查看,大部份内容是一些细节的处理。app
是否是以为内容有些爆炸,有些多。确实有些,不如,先去喝杯水,上个洗手间,放松一下,咱们再继续。dom
咱们在看到后面的源码( connect.js
)以前须要先看一些内容,还记得 connect
函数执行的时候传入的参数相似于 mapStateToProps
或者 mapDispatchToProps
的过滤参数,源码中我会移除这部分内容,因此我在开头先来举例说明。
源码中以 match
函数来对参数进行识别,以条件选项最多的 mapDispatchToProps
为例子。
即对入参进行以下顺序校验,优先知足即返回:
whenMapDispatchToPropsIsFunction
传入识别为函数whenMapDispatchToPropsIsMissing
未传入whenMapDispatchToPropsIsObject
传入识别为对象在了解过这个后,咱们来回到源码:
export function createConnect({ connectHOC = connectAdvanced/* HOC */, 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') // 对 connectAdvanced 的封装 return connectHOC(selectorFactory, { // used in error messages methodName: 'connect', // used to compute Connect's displayName from the wrapped component's displayName. getDisplayName: name => `Connect(${name})`, // if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes shouldHandleStateChanges: Boolean(mapStateToProps), // passed through to selectorFactory initMapStateToProps, initMapDispatchToProps, initMergeProps, pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, // any extra options args can override defaults of connect or connectAdvanced ...extraOptions }) } } export default /*#__PURE__*/ createConnect() 复制代码
那么到此,咱们已经看完 connect
函数的内容。
回到前面的内容,整个高阶组件函数是为了实现:
export default connectAdvanced((dispatch, options) => (state, props) => ({ thing: state.things[props.thingId], saveThing: fields => dispatch(actionCreators.saveThing(props.thingId, fields)), }))(YourComponent) 复制代码
相似于这样的代码,因此到目前为止是否是漏了点什么?是的,被你发现了。是 selectorFactory
:
export function impureFinalPropsSelectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch ) { return function impureFinalPropsSelector(state, ownProps) { return mergeProps( mapStateToProps(state, ownProps), mapDispatchToProps(dispatch, ownProps), ownProps ) } } 复制代码
我省略了大部分优化代码,很直观的理解就是过滤、合并最终你想要的 props
。剩下关于源码中 Hook 便是一些简单的封装,这里就不作解读了,很简单,若是感兴趣的话,请移步至 Github 仓库查看,那么此次的 React Redux 源码学习到此,感谢阅读至此!