共计 40480 个字符,预计需要花费 102 分钟才能阅读完成。
前言
后面学习 redux、redux-thunk
的源码,接下来再接再厉,来理解一下 react-redux
的源码。
p.s. 这里钻研的源码版本号是:7.2.4
作用
官网文档有这么一段话,翻译过去的意思是:redux
是独立的库,它能够用于多种库或者框架中,咱们将它与某种 UI 库(框架)联合应用的时候,比起在 UI 库中操作 redux
,咱们往往会应用一种UI binding
库去作为它们之间的桥梁。
Redux itself is a standalone library that can be used with any UI layer or framework, including React, Angular, Vue, Ember, and vanilla JS. Although Redux and React are commonly used together, they are independent of each other.
If you are using Redux with any kind of UI framework, you will normally use a “UI binding” library to tie Redux together with your UI framework, rather than directly interacting with the store from your UI code.
它的次要作用有:
react-redux
还是redux
官网团队保护的,当redux
更新时,这个库也会跟着更新;- 进步了性能,通常状况下,当一个
component
产生扭转时,整棵树都会从新渲染一遍,然而react-redux
外部帮咱们做了优化,进步了性能; - 社区弱小。
源码剖析
源码 src/index.js
文件中裸露进去的有:
export {
Provider,
connectAdvanced,
ReactReduxContext,
connect,
batch,
useDispatch,
createDispatchHook,
useSelector,
createSelectorHook,
useStore,
createStoreHook,
shallowEqual,
}
ReactReduxContext
这个源码很简略,就是应用 React.createContext
创立了一个全局上下文。所以 react-redux
实质上就是借助 context
,须要借助Context
给嵌套的组件传递 store
,还须要当store
中的 state
更新时,让嵌套的子组件的 state
也更新,所以前期会 在context
中保留 store
和description
,简化了 redux
应用。
// src/components/Context.js
import React from 'react'
export const ReactReduxContext = /*#__PURE__*/ React.createContext(null)
if (process.env.NODE_ENV !== 'production') {ReactReduxContext.displayName = 'ReactRedux'}
export default ReactReduxContext
batch
这是一个变量,跟 react
中的批量更新无关。批量更新,依照我现阶段的接触是这么了解的:若是间断调用 N 次更新,react
不会每次都触发视图重渲染,而是会等更新都间接完取得最终后果的时候才会更新视图。
// src/utils/reactBatchedUpdates
export {unstable_batchedUpdates} from 'react-dom'
// src/index.js
import {unstable_batchedUpdates as batch} from './utils/reactBatchedUpdates'
Provider
Provider
的作用就是,你能够在父级组件中将 store
的状态注入,而后父组件包裹的任意组件都能够应用到 store
中的状态,而不必每个文件都要手动援用store
。
ReactDOM.render(<Provider store={store}>
<App />
</Provider >,
document.getElementById('root')
);
Provider
是一个组件,那么根本构造跟一般的组件无异,依据文档可知,它承受三个参数:store
、chidren(个别是咱们的 App 组件)
、context
,(如果这样提供了 context
的话,还须要为所有连贯的组件提供雷同的context
)。
上文咱们晓得了 react-redux
次要是使用到了 context
,这里咱们不难剖析出Provider
的根本架构:
import {ReactReduxContext} from './Context'
function Provider({store, context, children}){
const Context = context || ReactReduxContext;
return (<Context.Provider value={store}>{children}</Context.Provider>
)
}
在 Provider
中咱们须要对 Context
和store
来做一些解决,在此之前,咱们先来剖析几个小办法:
createListenerCollection
从函数名字来看,就是创立一个 listener
收集器,次要作用就是创立链表,每个节点有 callback
回调函数和双指针:前一个节点的指针(prev
)和下一个节点的指针(next
)。
createListenerCollection
内申明一个 first
指向这个链表的头部,last
指向这个链表的尾巴,还定义了以下几个办法:
clear
:革除first
和last
指针,即清空链表;notify
:遍历listener
中的每个节点,调用他们的callback
。这里还应用到了下面提到的batch
。猜想是遍历节点调用函数,batch
能够确保函数都调用实现之后再一次性更新,达到批处理的作用,而不是每执行一次回调函数,就更新一次;get
:遍历整个链表,将每个节点顺次保留在listeners
数组中,并返回;-
subscribe
:新增监听的节点,先设置isSubscribed = true
,表明曾经监听,而后像链表新增节点一样新增节点:- 创立一个新节点,含有
callback、prev 和 next
属性,其中prev
赋值为last
(因为新节点会放在最初一个节点前面嘛); - 判断当初的链表是不是空的,如果是空的,那么这个节点就是头节点,否则,就将以后节点的上一个节点的
next
指向这个节点。
最初还会返回一个勾销监听的函数,勾销监听这个节点:
- 如果
isSubscribed
为false
或者first
(链表)为空,就间接返回; - 设置
isSubscribed
为false
; -
删除逻辑可能有点绕,咱们举个例子,以后节点是 B:
- 如果
B
有下一个节点C
,那么咱们将C
的prev
指向B
的上一个节点; - 否则,即
B
没有下一个节点,那么阐明此时last
指向的是B
, 那么应该将last
指向B
的上一个节点; - 如果
B
有上一个节点A
, 那么A
的next
应该指向B
的下一个节点; - 否则,阐明
B
即在链表头,此时first
指向的就是B
,应该批改为B
的下一个节点。
- 如果
- 创立一个新节点,含有
import {getBatch} from './batch'
function createListenerCollection() {
// batch 是批处理,react 的更新机制跟 batch 无关,当有多个值一起更新时,会将他们合并起来一次更新,而不是每次更新一个值就从新渲染
const batch = getBatch()
let first = null // 头指针
let last = null // 尾指针,阐明这个时双向链表
return {
// 清空指针,即链表清空
clear() {
first = null
last = null
},
notify() {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 = { // listener 是一个节点,含有双向指针
callback,
next: null, // 新增的节点,尾巴是空节点,所以是 null
prev: last, // 新增节点的前一个节点,必定是上一个节点
})
if (listener.prev) {
// 如果新增的节点有前一个节点的话,那么它上一个节点要用 next 指针指向它
listener.prev.next = listener
} else {
// 没有的话,阐明新增的节点就算第一个节点
first = listener
}
return function unsubscribe() {
// 如果没有订阅,或者链表是空的,那么就不必勾销监听啦
if (!isSubscribed || first === null) return
// 勾销订阅
isSubscribed = false
// 因为删除了该节点,须要批改它的前后节点的指针指向
// 兴许还须要批改 first 和 last 指针
if (listener.next) {listener.next.prev = listener.prev} else {last = listener.prev}
if (listener.prev) {listener.prev.next = listener.next} else {first = listener.next}
}
},
}
}
Subscription
Provider
将 store
中的 state
传入到各个组件中并在 state
更新的时候同时让各个组件中的 store
也产生扭转,那么须要实时监听 store
并且反对解除绑定监听的操作。所以外部封装一个名为 Subscription
的函数,这个函数的意思是 订阅。这个函数前期在其余办法实现中也会用到,所以须要理清它的思路。
encapsulates the subscription logic for connecting a component to the redux store, as well as nesting subscriptions of descendant components, so that we can ensure the ancestor components re-render before descendants
封装用于将组件连贯到 redux 存储的订阅逻辑,以及对后辈组件的嵌套订阅,以便咱们可能确保先人组件在后辈组件之前 re-render
// src/utils/Subscription.js
const nullListeners = {notify() {}}
export default class Subscription {constructor(store, parentSub) {
this.store = store // redux 中的 store
this.parentSub = parentSub // context.store
this.unsubscribe = null // 订阅对象
this.listeners = nullListeners // 订阅回调函数
this.handleChangeWrapper = this.handleChangeWrapper.bind(this)
}
// 给嵌套的组件减少监听
addNestedSub(listener) {this.trySubscribe()
return this.listeners.subscribe(listener)
}
// 告诉嵌套组件 state 产生了扭转
notifyNestedSubs() {this.listeners.notify()
}
// 当 store 扭转时执行的回调函数
handleChangeWrapper() {if (this.onStateChange) {this.onStateChange()
}
}
// 判断是否订阅了
isSubscribed() {return Boolean(this.unsubscribe)
}
// 新增订阅
// 如果没有订阅,那么进行订阅,其中 parentSub 的优先级比 store 高
trySubscribe() {if (!this.unsubscribe) {
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
}
}
}
综上所述,Subscription
的作用就是传入 store
并创立一个 listeners
对象,来对 store
进行监听并告诉各个子组件,或者传入parentSub
, 新增监听节点,并遍历
useIsomorphicLayoutEffect
在钻研 useIsomorphicLayoutEffect
之前,咱们先来捋一下 useEffect
和useLayoutEffect
的区别:
useEffect
: 当render
完结之后,callback
函数会被执行,因为是异步的,所以不会阻塞浏览器的渲染;useLayoutEffect
: 如果你在useEffect
中须要解决DOM
操作,能够应用useLayoutEffect
, 不会呈现闪屏的状况。它会在DOM
实现后立刻执行,然而会在浏览器进行任何绘制之前执行,所以会阻塞浏览器的渲染,是同步渲染;- 无论
useLayoutEffect
还是useEffect
都无奈在Javascript
代码加载实现之前执行。在服务端渲染组件中引入useLayoutEffect
代码时会触发React
正告。解决这个问题,须要将代码逻辑移至useEffect
中。
Provider
中
import {useEffect, useLayoutEffect} from 'react'
export const useIsomorphicLayoutEffect =
typeof window !== 'undefined' &&
typeof window.document !== 'undefined' &&
typeof window.document.createElement !== 'undefined'
? useLayoutEffect
: useEffect
为了解决下面的问题,浏览器渲染应用的是 useLayoutEffect
,服务器端渲染应用的是useEffect
。应用useLayoutEffect
是为了确保 store
的订阅回调函数的执行产生在最初一次的 render commit
,否则当store
的更新产生在 render
和effect
之间时,咱们可能会失落更新。
咱们也须要确保 subscription
创立的时候是异步的,否则当 store
的更新产生在 subscription
的创立前,咱们察看到的 state
可能与 store
中的不统一。
源码
import React, {useMemo} from 'react'
import PropTypes from 'prop-types'
import {ReactReduxContext} from './Context'
import Subscription from '../utils/Subscription'
import {useIsomorphicLayoutEffect} from '../utils/useIsomorphicLayoutEffect'
function Provider({store, context, children}) {
// useMemo 会在依赖项产生扭转的时候,从新调用外部的函数
// 所以当 store 产生扭转的时候,会从新计算 contextValue 的值
const contextValue = useMemo(() => {
// 创立 Subscription 对象
const subscription = new Subscription(store)
// 将 notifyNestedSubs 绑定在 onStateChange
subscription.onStateChange = subscription.notifyNestedSubs
// 敲重点,在 react-redux 中,contextValue 的值都含有这两个属性
// subscription 是一个监听对象,当 store 产生扭转时,能够通过 subscription
// 给订阅了 store 的组件传递信息、也能够勾销监听
return {
store,
subscription,
}
}, [store])
// 当 store 产生扭转的时候,从新计算以后的 getState 的值
const previousState = useMemo(() => store.getState(), [store])
// 当 previousState 或者 contextValue 产生扭转的时候触发上面的逻辑
useIsomorphicLayoutEffect(() => {const { subscription} = contextValue
// 遍历链表的每个节点,触发回调函数,给订阅了 store 的组件公布告诉
// 之后创立一个新的链表
subscription.trySubscribe()
// 可能公布告诉之后 state 的值产生扭转,// 持续遍历链表,告诉
if (previousState !== store.getState()) {subscription.notifyNestedSubs()
}
return () => {
// 勾销监听,便于垃圾回收机制回收
subscription.tryUnsubscribe()
subscription.onStateChange = null
}
}, [contextValue, previousState])
// 如果咱们没传递 context,就默认应用 ReactReduxContext
const Context = context || ReactReduxContext
return <Context.Provider value={contextValue}>{children}</Context.Provider>
}
export default Provider
从源码能够看进去 Provider
是一个函数,即能够充当组件,外部次要执行了几个步骤:
- 给
store
创立了一个subscription
对象,便于store
更新的时候借助subscription
进行告诉,并将两个赋值给ContextValue
; - 应用
Context.Provider
将ContextValue
传递给Children
。
这里能够抛出一个疑难,如果 react-redux 中是借助 context 来传递 store 的话,那么如果同时要反对自定义的 context 的话,外部做了什么解决?
当在 Provider
中应用 Context
时候,先创立一个 Conetxt
,将其作为参数传入Provider
中,然而肯定要在子组件中手动引入这个 Context
不然就会报错:
import React from 'react';
export const ColorContext = React.createContext({color: 'red'});
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import store from './store';
import {Provider} from 'react-redux';
import {ColorContext} from './store/context';
ReactDOM.render(<Provider store={store} context={ColorContext}>
<App />
</Provider >,
document.getElementById('root')
);
为了防止谬误,在子组件中,context
作为 props
传入:
// App.js
<Count context={ColorContext} />
为了在外部读取到 ColorContext
, 能够再ownProps
中获取:
// Counter.js
function Counter(props){const { count, increment, ColorContext} = props;
const contextValue = useContext(ColorContext);
console.log('contextValue123', contextValue);
return (
<>
<p>{count}</p>
<button onClick = {() => increment(1)}>+</button>
</>
)
}
function mapStateToProps(state, ownProps) {
return {
count: state.count,
ColorContext: ownProps.ownProps
}
}
function mapDispatchToProps(dispatch) {return { increment: bindActionCreators(actionCreators.increment, dispatch) }
}
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
另外依据前面的源码可得, 这样也不会报错 …,然而无奈在 ownProps
取得Context
<Count store={store} />
connectAdvanced
这是一个素来没有用过的 API
!connect
的底层是靠它实现的,不过官网文档说,将来可能会将它从代码中移除。connectAdvanced
的跟 connect
的区别在于,并没有规定如何将 state、props 和 dispatch
传入到最终的 props
,也没有对产生的props
做缓存来优化性能。
connectAdvanced
接管两个参数:selectorFactory
和 factoryOptions
。在selectorFactory
咱们能够实现将什么样的参数传入到组件中,这些参数能够依赖于 store.getState()
、action
和组件本身的props
。官网DEMO
:
function selectorFactory(dispatch) {let ownProps = {}
let result = {}
const actions = bindActionCreators(actionCreators, dispatch)
const addTodo = (text) => actions.addTodo(ownProps.userId, text)
return (nextState, nextOwnProps) => {const todos = nextState.todos[nextOwnProps.userId]
const nextResult = {...nextOwnProps, todos, addTodo}
ownProps = nextOwnProps
if (!shallowEqual(result, nextResult)) result = nextResult
return result
}
}
export default connectAdvanced(selectorFactory)(TodoApp)
咱们权且将 selectorFactory
中返回的函数叫做 selector
吧。selector
函数接管两个参数,新的 state
和组件本身的 props
。selector
函数会在组件接管到新的 state
或者 props
时被调用,而后返回一个简略对象给被包裹的组件。
而 factoryOptions
中的参数能够有这几种:
getDisplayName
: 取得被connectAdvanced
包裹后返回的新组件的名字,默认是ConnectAdvanced('+name+')
methodName
: 用来显示在错误信息中的,默认是connectAdvanced
;shouldHandleStateChanges
:是否要跟踪state
的变动,默认值是true
;forwardRef
:当须要应用ref
来保留被connectAdvanced
包裹之后的新组件时,要设置为false
;context
:createContext
创立的context
;- 其余参数:会被传递给
selectorFactory
即selector
办法的第二个参数中。
这里上一下源码的根本架构,有些行将要被移除的参数被我删掉了!
export default function connectAdvanced(
selectorFactory,
{getDisplayName = (name) => `ConnectAdvanced(${name})`,
methodName = 'connectAdvanced',
shouldHandleStateChanges = true,
forwardRef = false,
context = ReactReduxContext,
// 残余的参数会传递给 selectorFactory
...connectOptions
}
){
// ...
return function wrapWithConnect(WrappedComponent) {
// ...
return hoistStatics(Connect, WrappedComponent)
}
}
hoistStatics
来源于 hoist-non-react-statics
这个包,它能够将你原先组件中的动态属性复制给 connectAdvanced
包裹后的新组件。举个栗子:
const Component.staticMethod = () => {}
const WrapperComponent = enhance(Component);
typeof WrapperComponent.staticMethod === undefined // true
// 解决办法如下
function enhance(Component) {class WrapperComponent extends React.Component {/*...*/}
WrapperComponent.staticMethod = Component.staticMethod;
return Enhance;
}
// 过后若咱们不晓得原组件的所有动态属性值,下面的办法便有效了,// 所以能够借助 hoist-non-react-statics
import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(Component) {Class WrapperComponent extends React.Component {/* ... */}
hoistNonReactStatic(WrapperComponent, Component);
return WrapperComponent;
}
持续剖析,来了解一下外部的每行代码:
export default function connectAdvanced(){
// 这里的前半部分,都对行将移除的属性做报错...
const Context = context; // 保留要传递到组件外部的 context
return function wrapWithConnect(WrappedComponent) {// ...}
}
进入到 wrapWithConnect
源码:
function wrapWithConnect(WrappedComponent) {
// 先取得组件的名字
const wrappedComponentName =
WrappedComponent.displayName || WrappedComponent.name || 'Component'
// 给被包裹之后返回的新组件的起名字 (name) => `ConnectAdvanced(${name})`
const displayName = getDisplayName(wrappedComponentName)
const selectorFactoryOptions = {
...connectOptions,
getDisplayName,
methodName,
shouldHandleStateChanges,
displayName,
wrappedComponentName,
WrappedComponent,
}
const {pure} = connectOptions
// 创立咱们的 selector 函数
function createChildSelector(store) {return selectorFactory(store.dispatch, selectorFactoryOptions)
}
// 依据 pure 来判断是否用 useMemo 来进步被包裹返回的新组件的性能
const usePureOnlyMemo = pure ? useMemo : (callback) => callback()
function ConnectFunction(props){}
// 依据 pure 来判断是否用 React.memo 来进步被包裹返回的新组件的性能
const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction
Connect.WrappedComponent = WrappedComponent
Connect.displayName = ConnectFunction.displayName = displayName
// ...
}
这里有一个 ConnectFunction
函数,源码巨长,持续剖析:
function ConnectFunction(props) {
// 从 props 中解构出了 context、ref 和残余的 Props
const [
propsContext,
reactReduxForwardedRef,
wrapperProps,
] = useMemo(() => {const { reactReduxForwardedRef, ...wrapperProps} = props
return [props.context, reactReduxForwardedRef, wrapperProps]
}, [props])
// 之前有 context = ReactReduxContext; const Context = context
// Provider 中用户能够传入自定义的 Context,如果没有就默认是 ReactReduxContext
// 用户如果之前在 Provider 传入 context, 须要在嵌套组件中手动传入该 Context, 故在这里咱们须要对 Context 的值进行判断,// 应用适合的 Context
const ContextToUse = useMemo(() => {
return propsContext &&
propsContext.Consumer &&
isContextConsumer(<propsContext.Consumer />)
? propsContext
: Context
}, [propsContext, Context])
// 取得上下文中的值
const contextValue = useContext(ContextToUse)
// 后面讲 Provider 的时候提到过了
// Provider 中封装了 contextValue, 外部含有 store 和 subscription
// 如果用户传了自定义的 Context, 嵌套组件必须传通过 Context 或者是 store 才不会报错。// 这里要判断 contextValue.store 的起因就是
// 如果没有 store,则须要从 contextValue 中找 store,如果 contextValue 中也没有
// 就会报错
// 所以这就是为什么如果用户传递了 context,那么须要在嵌套组件中自定义传入 context 或者 store 的起因
const didStoreComeFromProps =
Boolean(props.store) &&
Boolean(props.store.getState) &&
Boolean(props.store.dispatch)
const didStoreComeFromContext =
Boolean(contextValue) && Boolean(contextValue.store)
if (
process.env.NODE_ENV !== 'production' &&
!didStoreComeFromProps &&
!didStoreComeFromContext
) {
throw new Error(
`Could not find "store" in the context of ` +
`"${displayName}". Either wrap the root component in a <Provider>, ` +
`or pass a custom React context provider to <Provider> and the corresponding ` +
`React context consumer to ${displayName} in connect options.`
)
}
// 保留 store
const store = didStoreComeFromProps ? props.store : contextValue.store
// 后面提到了,如果 store 产生扭转,那么会调用 selector 函数
const childPropsSelector = useMemo(() => {return createChildSelector(store)
}, [store])
// ...
}
const [subscription, notifyNestedSubs] = useMemo(() => {// 如果不须要监听 state 的变动,则返回[null, null]
if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY
// 如果 store 是由 context 提供的话,须要对 contextValue.subscription 进行监听
const subscription = new Subscription(
store,
didStoreComeFromProps ? null : contextValue.subscription
)
// 复制 notifyNestedSubs
const notifyNestedSubs = subscription.notifyNestedSubs.bind(subscription)
return [subscription, notifyNestedSubs]
}, [store, didStoreComeFromProps, contextValue])
// 笼罩以后的 contextValue
const overriddenContextValue = useMemo(() => {
// 如果 store 是来自 Props,那么间接返回 contextValue
if (didStoreComeFromProps) {return contextValue}
// 否则,将该组件的 subscription 放入上下文中
return {
...contextValue,
subscription,
}
}, [didStoreComeFromProps, contextValue, subscription])
// 当 store 更新时,须要强制更新被被包裹的组件也进行更新,进而对子组件也进行更新
const [[previousStateUpdateResult],
forceComponentUpdateDispatch,
] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)
// 当产生谬误的时候会抛出异样,失常状况的话 previousStateUpdateResult 应该是 null
if (previousStateUpdateResult && previousStateUpdateResult.error) {throw previousStateUpdateResult.error}
// 这里贴一下各个参数的源码
function storeStateUpdatesReducer(state, action) {const [, updateCount] = state
return [action.payload, updateCount + 1]
}
const EMPTY_ARRAY = []
const initStateUpdates = () => [null, 0]
// 后面提到过,useIsomorphicLayoutEffect 外部判断,// 如果是服务端返回 useEffect
// 如果是浏览器端返回 useLayoutEffect
import {useIsomorphicLayoutEffect} from '../utils/useIsomorphicLayoutEffect'
// 这里能够了解成,调用了 useEffect/useLayoutEffect,不过参数都是动静的
function useIsomorphicLayoutEffectWithArgs(
effectFunc,
effectArgs,
dependencies
) {useIsomorphicLayoutEffect(() => effectFunc(...effectArgs), dependencies)
}
// 持续剖析源码
const lastChildProps = useRef() // 更新之前传入以后组件的 props(Component)const lastWrapperProps = useRef(wrapperProps) // 更新之前新组件的 props (ConnectComponent)
const childPropsFromStoreUpdate = useRef() // 判断组件的更新是否是因为 store 的更新
const renderIsScheduled = useRef(false)
// usePureOnlyMemo 是依据 pure 来判断是否应用 memo
const actualChildProps = usePureOnlyMemo(() => {// 视图更新可能是因为 store 更新导致的会让组件 (Component)取得新的 props
// 如果 Component 有新的 props, 而 ConnectComponent 的 props 不变,那么应该应用新的 props, 这样咱们能够取得 ConnectComponent 的新 props
// 然而如果咱们有了新的 ConnectComponent props, 它可能会扭转 Component 的 props, 那么就会从新进行计算
// 为了避免出现问题,只有当 ConnectComponent 的 props 不变时,咱们才会依据 Component 的新 props 进行更新
if (
childPropsFromStoreUpdate.current &&
wrapperProps === lastWrapperProps.current
) {return childPropsFromStoreUpdate.current}
// 调用 selector 函数
return childPropsSelector(store.getState(), wrapperProps)
}, [store, previousStateUpdateResult, wrapperProps])
// 咱们须要上面的函数来执行同步到渲染,然而在 SSR 中应用 useLayoutEffect 会报错,这里咱们须要检测一下,如果是 ssr,那么就改为应用 effect 来防止这个正告
useIsomorphicLayoutEffectWithArgs(captureWrapperProps, [
lastWrapperProps,
lastChildProps,
renderIsScheduled,
wrapperProps,
actualChildProps,
childPropsFromStoreUpdate,
notifyNestedSubs,
])
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()}
}
// 当 store 或者 subscription 产生扭转时,咱们从新订阅
useIsomorphicLayoutEffectWithArgs(
subscribeUpdates,
[
shouldHandleStateChanges,
store,
subscription,
childPropsSelector,
lastWrapperProps,
lastChildProps,
renderIsScheduled,
childPropsFromStoreUpdate,
notifyNestedSubs,
forceComponentUpdateDispatch,
],
[store, subscription, childPropsSelector]
)
function subscribeUpdates(
shouldHandleStateChanges, // 是否对 store 的更新进行订阅
store, // store
subscription, // subscription
childPropsSelector, // selector
lastWrapperProps, // 上一次组件的 props
lastChildProps, // 上一次的子 props
renderIsScheduled, // 是否正在调度
childPropsFromStoreUpdate // 判断子 props 是否是来自 store 的更新
notifyNestedSubs, // notifyNestedSubs
forceComponentUpdateDispatch
) {
// 如果没有订阅,间接返回
if (!shouldHandleStateChanges) return
// 捕捉值,并查看是否以及何时卸载该组件
let didUnsubscribe = false
let lastThrownError = null
// 当每次 store 更新流传到这个组件时,咱们就会调用这个函数
const checkForUpdates = () => {if (didUnsubscribe) {
// 如果曾经勾销订阅,间接返回
return
}
// 取得更新前的 store
const latestStoreState = store.getState()
let newChildProps, error
try {
// 调用 selector 来取得最新的子 props
newChildProps = childPropsSelector(
latestStoreState,
lastWrapperProps.current
)
} catch (e) {
error = e
lastThrownError = e
}
if (!error) {lastThrownError = null}
// 如果新旧 props 一样,那么就不做任何解决
if (newChildProps === lastChildProps.current) {if (!renderIsScheduled.current) {// 调用 subscription.notifyNestedSubs()
notifyNestedSubs()}
} else {
// 用 ref 来存储最新的 props,如果应用 useState/useReducer 来追踪,// 那么将无奈确定这个值是否会加工过
// 也无奈清空这个值触发进行强制渲染
lastChildProps.current = newChildProps
childPropsFromStoreUpdate.current = newChildProps
renderIsScheduled.current = true
// 如果子 props 被更新了,那么能够从新渲染了
forceComponentUpdateDispatch({
type: 'STORE_UPDATED',
payload: {error,},
})
}
}
subscription.onStateChange = checkForUpdates
subscription.trySubscribe()
// 将 store 中产生扭转的值进行视图更新
checkForUpdates()
const unsubscribeWrapper = () => {
didUnsubscribe = true
subscription.tryUnsubscribe()
subscription.onStateChange = null
if (lastThrownError) {
// 当 store 更新,然而某个组件没有依赖 store 中更新的 state 切 mapState 办法有问题时
// 就会报错
throw lastThrownError
}
}
return unsubscribeWrapper
}
// 回到次要源码中,咱们曾经解决完了须要传递的参数,接下来就是渲染组件
// 还应用了 useMemo 来优化性能
const renderedWrappedComponent = useMemo(() => (
<WrappedComponent
{...actualChildProps}
ref={reactReduxForwardedRef}
/>
),
[reactReduxForwardedRef, WrappedComponent, actualChildProps]
)
// 如果 React 看到与上次完全相同的元素援用,它会退出从新出现子元素,就像它被包装在 React.memo()中或从 shouldComponentUpdate 返回 false 一样。const renderedChild = useMemo(() => {if (shouldHandleStateChanges) {
// 如果须要依据 store 的变动更新组件,那么就应用 context.provider 封装组件
// 并传入解决好的值
return (<ContextToUse.Provider value={overriddenContextValue}>
{renderedWrappedComponent}
</ContextToUse.Provider>
)
}
// 否则间接返回原先的组件
return renderedWrappedComponent
}, [ContextToUse, renderedWrappedComponent, overriddenContextValue])
return renderedChild
到这里 connectAdvanced
的源码就剖析结束了。咱们来捋一下思路,为了不便阐明我先写行代码:
function selector = (dispatch, ...args) => {}
const connectComponent = connectAdvanced(selector, options)(component)
-
connectAdvanced
是一个函数,接管一个selector
办法和options
对象:selector
办法是用户自定义的,接管一个dispacth
参数,最初须要返回一个含有入参为nextState 和 nextOwnProps
的函数,该外部函数最初会返回一个对象,对象的键值对就是行将传给component
组件的props
;options
对象,能够传递官网指定的,也能够自定义,如果是自定义的,最初会被当做传入selector
的第二个参数中。官网指定的参数中,有几个比拟重要,其中pure
的值确定了最初返回的组件是否要进行性能优化;connectAdvanced
最初返回的是一个高阶函数,参数就是要包裹的组件。
- 接下来对
options
中的值进行校验,并且对一些行将移除的属性做出正告解决; - 保留
options
中的context
; - 返回高阶函数
wrapWithConnect(WrappedComponent)
,参数就是要包裹的组件,接下来就是剖析wrapWithConnect(WrappedComponent)
中的内容了; - 先对传入的组件进行校验,判断是不是一个合格的组件;
- 记录被包裹的组件
component
的名字,进而给返回的新组件connectComponent
起名字; - 将
options
中多余的参数和定义好的参数封装起来,作为selector
的参数; - 依据
options
的参数pure
,用usePureOnlyMemo
变量来判断是否应用useMemo
办法; -
应用
ConnectFunction
创立一个Connect
组件,还给这个组件设置了WrappedComponent
和displayName
, 其中这个ConnectFunction
是connectAdvanced
中的外围:ConnectFunction
承受一个参数props
, 这个props
就是被包裹后的ConnetComponent
的参数props
;- 将
context、ref 和残余的参数
从props
中解耦进去; - 取得最终的
context
,并从中获得contextValue
; - 取得
store
; - 定义一个
memo
函数,当store
的值产生扭转,就调用用户自定义的selector
函数; - 取得最终的
ContextValue
的值; - 取得最终要返回被包裹的组件的
actualChildProps
: - 当依赖的值产生扭转时,应用
useEffect
或者useLayoutEffect
来调用captureWrapperProps
,外面将以后的connentComponent
的props
和store
的state
记录下来,不便下次渲染的时候比拟,如果store
的值产生更新,调用notifyNestedSubs
触发每个回调函数; - 当
store
或者subscription
进行更新时,发动告诉,subscribeUpdates
; - 最初判断
shouldHandleStateChanges
,来决定是否在里面包裹Context
,不须要则间接返回Connect
组件。
- 取得
Connect
组件后,通过判断forwardRef
, 如果存在,咱们须要应用React.forwardRef
来转发Connect
,否则间接应用Connect
,还应用了hoistStatics
来合并包裹前后的组件的动态属性。
小结
connectAdvanced
最初的后果是返回一个 Context.Comsumer
,其中须要对最初传入组件的props
参数进行解决,connectAdvanced
和 connect
最大的区别在于,外部的 pure
属性默认是false
,即它不会启动性能优化。
最初回到之前抛出的疑难:如果 react-redux 中是借助 context 来传递 store 的话,那么如果同时要反对自定义的 context 的话,外部做了什么解决?
react-redux
通过 Context
来保留 store
和subscription
,subscription
用来进行订阅公布 store
的,当须要联合 react-redux
搭配应用 context
时,记得再嵌套组件中将同个名称的 context
当作 props
传入组件中,这样 connectAdvanced
会将用户自定义的 Context
当作 props
合并到最终组件的 props
中。
所以尽管 react-redux
反对你能够全局搞一个 context
然而没必要,很鸡肋~
shallowEqual
在钻研 connect
源码前先学习一下shallowEqual
:
值 | === | is(x,y) |
---|---|---|
NaN、NaN | false | true |
0、+0 | true | true |
0、-0 | true | false |
+0、-0 | true | false |
// Object.is 能够对根本数据类型:null,undefined,number,string,boolean 做出十分准确的比拟,// 然而对于援用数据类型是没方法间接比拟的。function is(x, y) {if (x === y) {
// 解决 0、+ 0 和 - 0 的状况
return x !== 0 || y !== 0 || 1 / x === 1 / y
} else {
// 解决 NaN 的状况
return x !== x && y !== y
}
}
export default function shallowEqual(objA, objB) {
// 先用 is 来判断两个变量是否相等
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 和 in 运算符不同,该办法会疏忽掉那些从原型链上继承到的属性。!Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {return false}
}
return true
}
一开始先比拟,调用了 is
来判断两个值是否统一,这里兼容了一些常见的根本来信 ===
判断有问题的状况;之后判断是否不是对象或者是 null
,如果满足就返回false
,否则就遍历两个对象的key
进行比拟。从源码能够看出 浅比拟是不适用于嵌套类型的比拟的。
以上就是浅比拟的思维啦~
从 MDN 能够看出 is
是Object.is()
的一种 Polyfill
实现。
connect
看源码的时候顺便去看了官网文档,第一个感觉是我的英语真的提高了!第二个感觉是我以前是不是没翻过,怎么看到了很多忽略的中央??!
根本架构
// src/connect/connect.js
import connectAdvanced from '../components/connectAdvanced'
import shallowEqual from '../utils/shallowEqual'
import defaultMapDispatchToPropsFactories from './mapDispatchToProps'
import defaultMapStateToPropsFactories from './mapStateToProps'
import defaultMergePropsFactories from './mergeProps'
import defaultSelectorFactory from './selectorFactory'
export function createConnect({
onnectHOC = 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
} = {}){
// ...
return connectHOC()}
}
export default /*#__PURE__*/ createConnect()
导出前执行了 createConnect
办法,最初返回的是一个咱们意识的 connect
函数,外部调用了 HOC
函数。这外面波及到了秘稀稀拉拉的名字类似的参数,看的有点脑壳疼。
调用 createConnect()
时未传递参数,那么外面的参数都是读取默认值:
connectHOC = connectAdvanced,
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory,
而后返回了咱们属性的 connect
函数,它反对四个参数:mapStateToProps、mapDispatch、mergeProps 和一个对象
。由官网文档可知:
mapStateToProps
:函数类型,能够传入store.getState()
和以后组件本身的props
,最初须要返回一个对象;mapDispacthToProps
:函数类型,接管store.dispacth
和以后组件本身的props
,最初须要返回一个对象;mergeProps
: 函数类型,接管store.getState()
、store.dispacth
和组件本身的props
,最初须要返回对象;-
options
:这个对象外面有几个值:{ context?: Object, // 当咱们在 Provider 传入 context 时,在子组件中引入,就能够写在这里 pure?: boolean, // 为了进步 react-redux 的性能,默认开启 pure, 相似 pureComponent, 只有在组件本身的 props、mapStateToProps 的后果、或者是 mapDispatchToProps 的后果扭转时才会从新渲染组件 areStatesEqual?: Function, // 开启 pure 时,传递进来的 state 更新时的比拟函数 areOwnPropsEqual?: Function, // 开启 pure 时,本身 props 更新时的比拟函数 areStatePropsEqual?: Function, // 开启 pure 时,mapStateToProps 更新时的比拟函数 areMergedPropsEqual?: Function, // 开启 pure 时,mergeProps 的返回值更新时的比拟函数 forwardRef?: boolean, // 如果组件须要接管 ref,须要在这里设置为 true }
最初返回一个高阶函数,connectHOC
就是咱们之前介绍的 connectAdvanced
, 之前说的connectAdvanced
接管两个参数,一个是 selector
函数,一个是 options
对象:
return connectHOC(selectorFactory, {
// 为了在报错的时候应用
methodName: 'connect',
// 依据被 connect 包裹的组件的名字,给返回的新组件起名字
getDisplayName: (name) => `Connect(${name})`,
// 如果 mapStateToProps 为 Null, 那么就不须要监听更新 state 啦
shouldHandleStateChanges: Boolean(mapStateToProps),
// 上面这些值是给 selectorFactory 做为参数的
// 之前说的 connectAdvanced 的第二个参数 options 是个对象
// 外面除了规定的 key,其余属性最终都会成为 selector 函数的第二个参数
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
pure,
areStatesEqual,
areOwnPropsEqual,
areStatePropsEqual,
areMergedPropsEqual,
// any extra options args can override defaults of connect or connectAdvanced
...extraOptions,
})
逐渐认识一下 createConnect
中的默认参数:
connectHoc
它的默认值是connectAdvanced
,这个下面曾经理解过了,就不反复。
defaultMapStateToPropsFactories
在钻研之前来看一下这个源码中引入的文件wrapMapToProps
:
import verifyPlainObject from '../utils/verifyPlainObject'
export function wrapMapToPropsConstant(getConstant) {return function initConstantSelector(dispatch, options) {const constant = getConstant(dispatch, options)
function constantSelector() {return constant}
// dependsOnOwnProps 是判断以后的 mapStateToProps 的值,有没有依赖于 ownProps
constantSelector.dependsOnOwnProps = false
return constantSelector
}
}
// 有没有依赖于 ownProps
export function getDependsOnOwnProps(mapToProps) {
return mapToProps.dependsOnOwnProps !== null &&
mapToProps.dependsOnOwnProps !== undefined
? Boolean(mapToProps.dependsOnOwnProps)
: mapToProps.length !== 1
}
// 封装一个 mapToProps 代理函数
// 检测 selectorFactory 是否依据 ownProps 来更新。export function wrapMapToPropsFunc(mapToProps, methodName) {return function initProxySelector(dispatch, { displayName}) {const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
return proxy.dependsOnOwnProps
? proxy.mapToProps(stateOrDispatch, ownProps)
: proxy.mapToProps(stateOrDispatch)
}
// 容许 detectFactoryAndVerify 失去本人的 props
proxy.dependsOnOwnProps = true
proxy.mapToProps = function detectFactoryAndVerify(
stateOrDispatch,
ownProps
) {
proxy.mapToProps = mapToProps
proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
let props = proxy(stateOrDispatch, ownProps)
// props 返回函数,则解决 mapToProps,并将该新函数作为真正的 mapToProps 用于后续调用
if (typeof props === 'function') {
proxy.mapToProps = props
proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
props = proxy(stateOrDispatch, ownProps)
}
// 如果不是一个一般对象,就收回正告
if (process.env.NODE_ENV !== 'production')
verifyPlainObject(props, displayName, methodName)
return props
}
return proxy
}
}
// src/connect/mapStateToProps.js
import {wrapMapToPropsConstant, wrapMapToPropsFunc} from './wrapMapToProps'
export function whenMapStateToPropsIsFunction(mapStateToProps) {
return typeof mapStateToProps === 'function'
? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
: undefined
}
export function whenMapStateToPropsIsMissing(mapStateToProps) {return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined
}
export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]
mapStateToProps
返回一个数组,别离是 whenMapStateToPropsIsFunction
和whenMapStateToPropsIsMissing
,从函数名字可得,当 mapStateToProps
是函数或者是空的时候,别离调用的这两个函数。
defaultMapDispatchToPropsFactories
// src/connect/mapDispatchToProps.js
import bindActionCreators from '../utils/bindActionCreators'
import {wrapMapToPropsConstant, wrapMapToPropsFunc} from './wrapMapToProps'
export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
return typeof mapDispatchToProps === 'function'
? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps')
: undefined
}
export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
return !mapDispatchToProps
? wrapMapToPropsConstant((dispatch) => ({dispatch}))
: undefined
}
export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
return mapDispatchToProps && typeof mapDispatchToProps === 'object'
? wrapMapToPropsConstant((dispatch) =>
bindActionCreators(mapDispatchToProps, dispatch)
)
: undefined
}
export default [
whenMapDispatchToPropsIsFunction,
whenMapDispatchToPropsIsMissing,
whenMapDispatchToPropsIsObject,
]
mapDispatchToProps
返回一个数组,别离是 whenMapDispatchToPropsIsFunction
、whenMapDispatchToPropsIsMissing
和whenMapDispatchToPropsIsObject
,从函数名字可得,当 mapDispatchToProps
是函数、空或者是对象的时候的时候,别离调用的这三个函数。
defaultMergePropsFactories
// src/connect/mergeProps.js
import verifyPlainObject from '../utils/verifyPlainObject'
export function defaultMergeProps(stateProps, dispatchProps, ownProps) {return { ...ownProps, ...stateProps, ...dispatchProps}
}
export function wrapMergePropsFunc(mergeProps) {
return function initMergePropsProxy(
dispatch,
{displayName, pure, areMergedPropsEqual}
) {
let hasRunOnce = false
let mergedProps
return function mergePropsProxy(stateProps, dispatchProps, ownProps) {const nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps)
if (hasRunOnce) {if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps))
mergedProps = nextMergedProps
} else {
hasRunOnce = true
mergedProps = nextMergedProps
if (process.env.NODE_ENV !== 'production')
verifyPlainObject(mergedProps, displayName, 'mergeProps')
}
return mergedProps
}
}
}
export function whenMergePropsIsFunction(mergeProps) {
return typeof mergeProps === 'function'
? wrapMergePropsFunc(mergeProps)
: undefined
}
export function whenMergePropsIsOmitted(mergeProps) {return !mergeProps ? () => defaultMergeProps : undefined
}
export default [whenMergePropsIsFunction, whenMergePropsIsOmitted]
mergeProps
返回一个数组,别离是 whenMergePropsIsFunction
和whenMergePropsIsOmitted
,从函数名字可得,当 mergeProps
是函数或者是省略了的时候,别离调用的这两个函数。
defaultSelectorFactory
这个函数的名字翻译过去是:抉择工厂函数,就生成 connectAdvanced
须要的 selector
函数,selector
的第一个参数是dispatch
,第二个参数是对象,来瞄一下源码:
// import defaultSelectorFactory from './selectorFactory'
// selectorFactory.js
export default function finalPropsSelectorFactory(
dispatch,
{initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options}
) {
// 先初始化值
const mapStateToProps = initMapStateToProps(dispatch, options)
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
const mergeProps = initMergeProps(dispatch, options)
if (process.env.NODE_ENV !== 'production') {// ...}
// 依据 pure 应用不同的函数,并调用它们
const selectorFactory = options.pure
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
)
}
impureFinalPropsSelectorFactory
如果咱们没有开启 pure
的话,那么不做什么骚操作,间接返回mergeProps
,输入最初要传递给包裹后的组件的props
:
export function impureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch
) {return function impureFinalPropsSelector(state, ownProps) {
return mergeProps(mapStateToProps(state, ownProps),
mapDispatchToProps(dispatch, ownProps),
ownProps
)
}
}
pureFinalPropsSelectorFactory
当第一次执行这个函数的时候,会应用存储这次的 state、props
,再通过这两个值,去取得最初传给包裹组件的state
和props
合并后的props
;
如果曾经执行过的话,调用 handleSubsequentCalls
来比照此次更新是只更新 state
、只更新props
还是 state
和props
都更新,进而调用不同的逻辑,最初给包裹的组件最终的props
。
敲黑板了,上面的逻辑跟 react-redux
的性能无关 。咱们在应用connect
是能够传入 mapStateToProps、mapDispatchtoProps、mergeProps 和 Options
四个参数,其中前两个是必传的。
mapStateToProps
能够传入两个参数,state
和ownProps
,最初返回一个新的state
,所以咱们能够晓得新的state
不仅取决于store
的变动,也取决于ownProps
的变动,他们两个只有更新一个那么就会从新计算state
;mapDispatchtoProps
也反对传入两个参数,dispatch
和ownProps
, 返回一个新的dispatch
, 新的dispatch
的更新取决于dispacth
和ownProps
;mergeProps
是一个函数,默认状况下咱们都会省略,所以会被默认赋值,最初将新的state
、新的dispacth
和ownProps
变为新的props
传递给Connect
组件。
所以不论是 store
的更新还是组件本身 props
的更新,都会影响到被 connect
返回的新组件,这种性能开销是很大的,所以 react-redux
做了性能优化。
connect
比 connectAdvanced
多了一个 pure
的参数,默认是true
。是用来开启性能优化的。
export function pureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
{areStatesEqual, areOwnPropsEqual, areStatePropsEqual}
) {
let hasRunAtLeastOnce = false
let state
let ownProps
let stateProps
let dispatchProps
let mergedProps
// 首次执行,依据以后的 state 和 props,取得最终的 stateProps 和 dispatchProps
// 进而同个 mergedProps 取得最终传入组件 props
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 和 store 都扭转,调用这个函数
function handleNewPropsAndNewState() {
// 那么间接通过 mapStateToProps 取得新的 stateProps
stateProps = mapStateToProps(state, ownProps)
// mapDispatchToProps 办法如果有依赖 ownProps,那么也须要从新取得新的 dispatchProps
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
// 传递新的 stateProps、dispatchProps,通过 mergedProps 取得最终传入组件的 props
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
// 如果只有本身的 props 扭转
function handleNewProps() {
// 申明 mapStateToProps 时如果应用了 ownProps 参数同样会产生新的 stateProps!if (mapStateToProps.dependsOnOwnProps)
stateProps = mapStateToProps(state, ownProps)
// 申明 mapDispatchToProps 时如果应用了第二个参数(ownProps)这里会标记为 true
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
// 只有 store 扭转
function handleNewState() {
// 当 store 扭转,那么 mapStateToProps 会返回新的值
const nextStateProps = mapStateToProps(state, ownProps)
// 判断新的 stateProps 和上一次的 stateProps 是否相等
const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)
stateProps = nextStateProps
// 如果扭转,才须要从新计算最终的 props
if (statePropsChanged)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
// 如果不是第一次执行,function handleSubsequentCalls(nextState, nextOwnProps) {
// 判断组件本身的 Props 是否扭转
const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
// 判断 store 是否扭转
const stateChanged = !areStatesEqual(nextState, state)
// 记录下以后的 state 和 ownProps,不便下次更新比拟
state = nextState
ownProps = nextOwnProps
if (propsChanged && stateChanged) return handleNewPropsAndNewState()
if (propsChanged) return handleNewProps()
if (stateChanged) return handleNewState()
return mergedProps
}
return function pureFinalPropsSelector(nextState, nextOwnProps) {
// 判断是否执行过一次,来调用 handleSubsequentCalls 或者 handleFirstCall
return hasRunAtLeastOnce
? handleSubsequentCalls(nextState, nextOwnProps)
: handleFirstCall(nextState, nextOwnProps)
}
}
捋分明 selectorFactory
,接下来就是connectAdvanced
的执行流程了,就不复述了。
Hook
最初来聊一聊新增的 Hook
,6 月份我的项目还在啃老本,明明我的项目用的是hook
,然而对于react-redux
却齐全不晓得有 hook
相干的 api
存在,持续学习:
useSelector
useSelector
有点相似于 mapStateToProps
,能够从store
中获得咱们想要的 state
,然而不同的是mapStateToProps
用的是浅比拟,而 useSelector
用的是深比拟,只有 state
产生扭转,就必定会触发从新渲染,所以个别状况下,useSelector
只返回一个值,如果想要返回一个对象,倡议搭配 shallowEqual
进行应用,或者借助 reselect
创立一个记忆选择器,该选择器在一个对象中返回多个值,但只在其中一个值发生变化时返回一个新对象。
import {shallowEqual, useSelector} from 'react-redux'
import {createSelector} from 'reselect'
// 惯例用法
export const CounterComponent = () => {const counter = useSelector((state) => state.counter)
return <div>{counter}</div>
}
// 应用浅层比拟,取得一个对象
export const ObjComponent = () => {const obj = useSelector((state) => state.obj, shallowEqual)
return <div>{obj.counter}</div>
}
// 借助 reselect
const selectNumCompletedTodos = createSelector((state) => state.todos,
(todos) => todos.filter((todo) => todo.completed).length
)
export const CompletedTodosCounter = () => {const numCompletedTodos = useSelector(selectNumCompletedTodos)
return <div>{numCompletedTodos}</div>
}
export const App = () => {
return (
<>
<span>Number of completed todos:</span>
<CompletedTodosCounter />
</>
)
}
接下来开始剖析源码:
// useSelector 是由 createSelectorHook 返回的
export const useSelector = /*#__PURE__*/ createSelectorHook()
所以能够揣测出
createSelectorHook() = (selector, equalityFn) => selectedState
来看看createSelectorHook
:
export function createSelectorHook(context = ReactReduxContext) {// 后面 createSelectorHook()没有传递参数,所以 context = ReactReduxContext
const useReduxContext =
// 取得 context
context === ReactReduxContext
? useDefaultReduxContext
: () => useContext(context)
// 返回一个(selector, equalityFn = refEquality) => selectorState 的函数给 useSelector
// const refEquality = (a, b) => a === b useSelector 默认是严格比拟
return function useSelector(selector, equalityFn = refEquality) {
// 对 selector, equalityFn 进行校验
if (process.env.NODE_ENV !== 'production') {if (!selector) {throw new Error(`You must pass a selector to useSelector`)
}
if (typeof selector !== 'function') {throw new Error(`You must pass a function as a selector to useSelector`)
}
if (typeof equalityFn !== 'function') {
throw new Error(`You must pass a function as an equality function to useSelector`)
}
}
// 后面咱们看源码的时候晓得了,context 会存储 store 和 subscription
const {store, subscription: contextSub} = useReduxContext()
// 取得 selectedState 并返回
const selectedState = useSelectorWithStoreAndSubscription(
selector,
equalityFn,
store,
contextSub
)
useDebugValue(selectedState)
return selectedState
}
}
来看看 useSelectorWithStoreAndSubscription
是如何返回咱们想要的selectState
:
function useSelectorWithStoreAndSubscription(
selector,
equalityFn,
store,
contextSub // subscription
) {const [, forceRender] = useReducer((s) => s + 1, 0)
// 创立一个新的订阅对象
const subscription = useMemo(() => new Subscription(store, contextSub), [
store,
contextSub,
])
const latestSubscriptionCallbackError = useRef()
const latestSelector = useRef()
const latestStoreState = useRef()
const latestSelectedState = useRef()
// 取得当初的 store 中的 state
const storeState = store.getState()
let selectedState
try {
// 如果 selector 扭转了、或者 store 中的 state 扭转了、或者呈现谬误了
// 那么就从新执行 selector 取得新的 selectState
// 否则间接返回上一次的 selectState
if (
selector !== latestSelector.current ||
storeState !== latestStoreState.current ||
latestSubscriptionCallbackError.current
) {
// 将 new selectState 和 last selectState 比拟
// 如果统一,返回上一次的 selectState
// 否则,返回 new selectState
const newSelectedState = selector(storeState)
if (
latestSelectedState.current === undefined ||
!equalityFn(newSelectedState, latestSelectedState.current)
) {selectedState = newSelectedState} else {selectedState = latestSelectedState.current}
} 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
}
// 这个函数每次都被执行的时候,会保留这次的 selector、store 中的 state、selectState 和订阅捕捉谬误
// 便于下次比拟做应用
useIsomorphicLayoutEffect(() => {
latestSelector.current = selector
latestStoreState.current = storeState
latestSelectedState.current = selectedState
latestSubscriptionCallbackError.current = undefined
})
// 当 store 或者 subscription 产生扭转时
// 会调用 checkForUpdates 取得新的 storestate 和 selectState
// 比拟这次和上次的 selectState 的值
// 如果不等,更新 latestSelectedState 和 latestStoreState、latestSubscriptionCallbackError
useIsomorphicLayoutEffect(() => {function checkForUpdates() {
try {const newStoreState = store.getState()
const newSelectedState = latestSelector.current(newStoreState)
if (equalityFn(newSelectedState, latestSelectedState.current)) {return}
latestSelectedState.current = newSelectedState
latestStoreState.current = newStoreState
} catch (err) {
// we ignore all errors here, since when the component
// is re-rendered, the selectors are called again, and
// will throw again, if neither props nor store state
// changed
latestSubscriptionCallbackError.current = err
}
forceRender()}
subscription.onStateChange = checkForUpdates
subscription.trySubscribe()
checkForUpdates()
return () => subscription.tryUnsubscribe()
}, [store, subscription])
// 最初返回咱们须要的 selectedState
return selectedState
}
useSelector
外部次要通过 useSelectorWithStoreAndSubscription
来取得咱们想要的 selectState
,useSelectorWithStoreAndSubscription
通过比拟 selector
和store
判断是否要执行 selector
来取得最新的 selectState
, 而后将新获取到了selectState
与上一次 selectState
来做比拟,判断返回哪一个值。外部为了不便比拟,都会记录下以后的 selector、store、selectState、和捕捉的谬误
,目标就是、为了进步useSelector
的性能,不必每次数据明明跟之前一样却要返回新的内容导致视图更新。
useDispatch
useDiapatch
的实质就是取得 store
中的dispatch
。举一个官网栗子:
import React, {useCallback} from 'react'
import {useDispatch} from 'react-redux'
export const CounterComponent = ({value}) => {const dispatch = useDispatch()
const increaseCounter = useCallback(() => dispatch({ type: 'increase-counter'}), [])
return (
<div>
<span>{value}</span>
<button onClick={increaseCounter}>Increase counter</button>
</div>
)
}
间接看源码:
export function createDispatchHook(context = ReactReduxContext) {
const useStore =
context === ReactReduxContext ? useDefaultStore : createStoreHook(context)
return function useDispatch() {const store = useStore()
return store.dispatch
}
}
export const useDispatch = /*#__PURE__*/ createDispatchHook()
不难理解,先取得 Context
,再取得Context
中的store
,最初返回store.dispatch
。
useStore
在 useDispatch
中,有一个代码是 createStoreHook(context)
,其实createStoreHook
就是 useStore
的实现:
export function createStoreHook(context = ReactReduxContext) {
const useReduxContext =
context === ReactReduxContext
? useDefaultReduxContext
: () => useContext(context)
return function useStore() {const { store} = useReduxContext()
return store
}
}
export const useStore = /*#__PURE__*/ createStoreHook()
先取得 Context
,再取得Context
中的 store
,最初将store
返回~
小结
源码断断续续看了一周,还来回看了几遍,有点绕。官网尽管说在不久会废除 connectAdvanced
,然而为了connect
的实现,仍旧得好好啃啊。通过学习这次源码也对 useMemo
这个钩子加深了印象,晓得了 mapStateToProps 和 mapDispacthToProps
能够将 ownProps
当作第二个参数传入对性能的影响,最初就是学习到了外部新带 hook
,还接触到了reselect
这个库。
最初,肯定要多翻翻英文文档啊~
参考
- react-redux 官网
- 庖丁解牛 React-Redux(一): connectAdvanced
- Polyfill
- 你真的理解浅比拟么?
如有谬误,欢送指出,感激浏览~