react-redux常见用法
1 | import React from 'react' |
Provider
源码
- Provider源码地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29function Provider({ store, context, children }) {
const contextValue = useMemo(() => {
const subscription = new Subscription(store)
subscription.onStateChange = subscription.notifyNestedSubs
return {
store,
subscription
}
}, [store])
const previousState = useMemo(() => store.getState(), [store])
useEffect(() => {
const { subscription } = contextValue
subscription.trySubscribe()
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>
}
解释
- 把传入的store包装成Subscription对象,执行trySubscribe实现store的订阅,把store和subscription作为contextValue并且通过Context.Provider传给他的child,一般connect就会是Provider的子元素
- connect中会调用useContext获取store和subscription
connect
解释
- connect源码地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169// connect
function connect(
mapStateToProps,
mapDispatchToProps,
mergeProps,
{
pure = true,
areStatesEqual = strictEqual,
areOwnPropsEqual = shallowEqual,
areStatePropsEqual = shallowEqual,
areMergedPropsEqual = shallowEqual,
...extraOptions
} = {}
) {
...
return connectHOC(selectorFactory, {...})
}
// connectHOC其实就是connectAdvanced
function connectAdvanced(selectorFactory,connectOptions) {
return function wrapWithConnect(WrappedComponent) {
...
function ConnectFunction(props) {
// ...
const ContextToUse = useMemo(() => {
return propsContext &&
propsContext.Consumer &&
isContextConsumer(<propsContext.Consumer />)
? propsContext
: Context
}, [propsContext, Context])
const contextValue = useContext(ContextToUse)
// ...
const [
[previousStateUpdateResult],
forceComponentUpdateDispatch
] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)
//...
useIsomorphicLayoutEffectWithArgs(
subscribeUpdates,
[],
[]
)
// ...
const store = didStoreComeFromProps ? props.store : contextValue.store
const actualChildProps = usePureOnlyMemo(() => {
if (
childPropsFromStoreUpdate.current &&
wrapperProps === lastWrapperProps.current
) {
return childPropsFromStoreUpdate.current
}
return childPropsSelector(store.getState(), wrapperProps)
}, [store, previousStateUpdateResult, wrapperProps])
// ...
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
}
const contextValue = useContext(ContextToUse)
const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction
return hoistStatics(Connect, WrappedComponent)
}
}
//
function useIsomorphicLayoutEffectWithArgs(
effectFunc,
effectArgs,
dependencies
) {
useIsomorphicLayoutEffect(() => effectFunc(...effectArgs), dependencies)
}
function subscribeUpdates(
shouldHandleStateChanges,
store,
subscription,
childPropsSelector,
lastWrapperProps,
lastChildProps,
renderIsScheduled,
childPropsFromStoreUpdate,
notifyNestedSubs,
forceComponentUpdateDispatch
) {
const checkForUpdates = () => {
if (didUnsubscribe) {
// Don't run stale listeners.
// Redux doesn't guarantee unsubscriptions happen until next dispatch.
return
}
const latestStoreState = store.getState()
let newChildProps, error
try {
// Actually run the selector with the most recent store state and wrapper props
// to determine what the child props should be
newChildProps = childPropsSelector(
latestStoreState,
lastWrapperProps.current
)
} catch (e) {
error = e
lastThrownError = e
}
//...
lastChildProps.current = newChildProps
childPropsFromStoreUpdate.current = newChildProps
renderIsScheduled.current = true
// If the child props _did_ change (or we caught an error), this wrapper component needs to re-render
forceComponentUpdateDispatch({
type: 'STORE_UPDATED',
payload: {
error
}
})
}
// Actually subscribe to the nearest connected ancestor (or store)
subscription.onStateChange = checkForUpdates
subscription.trySubscribe()
// Pull data from the store after first render in case the store has
// changed since we began.
checkForUpdates()
const unsubscribeWrapper = () => {
didUnsubscribe = true
subscription.tryUnsubscribe()
subscription.onStateChange = null
if (lastThrownError) {
// It's possible that we caught an error due to a bad mapState function, but the
// parent re-rendered without this component and we're about to unmount.
// This shouldn't happen as long as we do top-down subscriptions correctly, but
// if we ever do those wrong, this throw will surface the error in our tests.
// In that case, throw the error from here so it doesn't get lost.
throw lastThrownError
}
}
return unsubscribeWrapper
}
解释
- hoistStatics方法的效果和Object.assign类似,就是把WrappedComponent上的所有非react内置的静态方法拷贝到Connect上,并且返回Connect
- 所以connect最后返回的是一个函数
wrapWithConnect
,这个函数以WrappedComponent为参数,所以才有connect(mapStateToProps, mapDispatchToProps)(App)
这样的用法 - 执行wrapWithConnect(App),最终返回的是React.memo(ConnectFunction)
- ConnectFunction最终返回的是renderedChild,renderedChild是一个ContextToUse.Provider,overriddenContextValue作为value传递下去,并且把WrappedComponent作为子元素,
actualChildProps
里的所有属性作为WrappedComponent的props。 actualChildProps
一般情况下就是childPropsFromStoreUpdate.current
,而childPropsFromStoreUpdate
是通过useRef来创建的,childPropsFromStoreUpdate.current
会在checkForUpdates
里面更新在ConnectFunction中,会执行useIsomorphicLayoutEffectWithArgs -> subscribeUpdates,subscribeUpdates里面会执行
subscription.onStateChange = checkForUpdates;subscription.trySubscribe()
,从而把实现store.subscribe(checkForUpdates)
的效果,从而每次store更新,就会执行checkForUpdates
,而checkForUpdates
中,如果计算得到的props发生了更改,则会重新生成newChildProp
,并且赋值给childPropsFromStoreUpdate.current
。然后调forceComponentUpdateDispatch
1
2
3
4
5
6
7
8
9
10
11
12
13newChildProps = childPropsSelector(
latestStoreState,
lastWrapperProps.current
)
if (newChildProps !== lastChildProps.current) {
childPropsFromStoreUpdate.current = newChildProps
forceComponentUpdateDispatch({
type: 'STORE_UPDATED',
payload: {
error
}
})
}forceComponentUpdateDispatch是通过以下代码创建的
1
2
3
4const [
[previousStateUpdateResult],
forceComponentUpdateDispatch
] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)所以最终会导致ConnectFunction的re-render
actualChildProps == childPropsFromStoreUpdate.current
,最终把更新好的props传递到WrappedComponent
redux的store更新是怎么传递过来的
- Provider会初始一个Subscription对象subscription_a,通过context传递到connect
- connect以subscription_a为父Subscription对象创建一个Subscription对象subscription_b
- 在connect中会执行subscription_b.trySubscribe时,此时会执行
this.parentSub.addNestedSub(this.handleChangeWrapper)
, - this.parentSub.addNestedSub又会执行this.trySubscribe(),从而把subscription_a的handleChangeWrapper方法执行到this.store.subscribe(this.handleChangeWrapper)
- 并且会初始化subscription_a.listeners数组,在执行subscription_a.addNestedSub时,会把this.listeners.subscribe(listener),这个listener就是subscription_b.handleChangeWrapper
- 所以store更新是执行subscription_a.handleChangeWrapper,而subscription_a.handleChangeWrapper里面会执行onStateChange(),subscription_a.onStateChange这个方法在Provider初始化的时候被设置为notifyNestedSubs,notifyNestedSubs方法执行的是this.listeners.notify(),从而把listeners的回调都执行一遍。其中就包含subscription_b.handleChangeWrapper。
- subscription_b.handleChangeWrapper里面执行的onStateChange,在connect中会被设置为checkForUpdates,从而实现store更新,最终把更新传递到connect的checkForUpdates