For those of you who have not read the Redux source code, take a look at my last article Redux principle analysis and source code analysis
This article is posted on my blog
The Github repository for this article
Since I learned higher-order components, it has been widely known that connect() is a higher-order component. Therefore, IN my long-term cognition, CONNECT () is just a simple higher-order component that filters state from context and passes it to its child components
Redux = reeducer; react-redux = connect(); react-redux = connect(); react-redux = connect(); React controls the re-rendering of data
Of course not, and the Connect () component is not simple, so I can say that THE first time I open the source code, it is a good analysis of the react-redux
The principle is briefly
Before analyzing the source code, we need to give a brief overview of the principle of react-Redux, otherwise it will be confusing to just look at the code
Let’s just draw a simple flow
React-redux provides a Subscription Subscription class that allows users to subscribe to updates from their parent and child levels
The outermost Provider component’s Context contains the store (which we passed in) and the generated Subscription instance that subscribes to Redux’s Subscrib ()
When we use connect(), a new component
// overriddenContextValue contains the Subscription instance for the new component and store < Component1.provider value={overriddenContextValue}> {WrappedComponent} </Component1.Provider>Copy the code
If the child component in
After the component is mounted, if there is an update to the Store, the Provider notifies the Subscription of the lower-level component, which notifies its lower-level component
<Provider store={store}>
<Component1>// The Provider to which it subscribes<Component2/>// It subscribes to Component1<Component1/>
</Provider>
// When the Store has updates, Provider notifies Component1, Component1 notifies Component2
Copy the code
When you subscribe, the parent Subscription is passed the method that updates its own component through the onStateChange() callback
Once the parent receives the notification, the onStateChange that subscribed to its own components is looping to update them
The idea behind the update is to use the mapStateToProps and mapDispatchToProps we passed in, and the built-in selectorFactor() to compare the state and props, and force the update itself whenever it changes. So the WrappedComponent we passed in is also forced to update
The principle is simple, look at the source code
Source analysis
Before going through the process, take a look at the Subscription class that runs through the React-Redux update process
Subscription
// Subscriotion.js
const nullListeners = { notify() {} };
// The listener collection is a bidirectional list
function createListenerCollection() {
// React unstable_batchedUpdates
// unstable_batchedUpdates will kill the forceUpdate of child components to prevent them from being re-rendered twice in a batch update
const batch = getBatch();
let first = null;
let last = null;
return {
clear() {
first = null;
last = null;
},
// Notify subscribers of updates
notify() {
batch((a)= > {
let listener = first;
while (listener) {
// The essence of this callback is to make the component itself forceUpdatelistener.callback(); listener = listener.next; }}); },/ / subscribe
subscribe(callback) {
let isSubscribed = true;
// Assign last to new
let listener = (last = {
callback,
next: null.prev: last
});
// If there is a previous one, next points to the current (the last one)
if (listener.prev) {
listener.prev.next = listener;
} else {
// Otherwise it is the first one
first = listener;
}
// Returns the unsubscribe function
return function unsubscribe() {
/ /... Unsubscribe logic}; }}; }export default class Subscription {
constructor(store, parentSub) {
// redux store
this.store = store;
// Parent Subscription instance
this.parentSub = parentSub;
// Unsubscribe the function
this.unsubscribe = null;
/ / listener
this.listeners = nullListeners;
this.handleChangeWrapper = this.handleChangeWrapper.bind(this);
}
// Add nested subscribers
addNestedSub(listener) {
// Start by binding the current Subscription instance to the parent
// The listeners are initialized at the same time
this.trySubscribe();
return this.listeners.subscribe(listener);
}
// Notify the child
notifyNestedSubs() {
this.listeners.notify();
}
// called when the parent Subscription is notified
handleChangeWrapper() {
// This is added when the new instance is created
if (this.onStateChange) {
this.onStateChange();
}
}
trySubscribe() {
// No double binding
if (!this.unsubscribe) {
this.unsubscribe = this.parentSub
? this.parentSub.addNestedSub(this.handleChangeWrapper)
: Subscribe is a method in redux that is called when the redux state changes
this.store.subscribe(this.handleChangeWrapper);
// Create new listeners. There are listeners for each connect component
this.listeners = createListenerCollection(); }}/ / unsubscribe
tryUnsubscribe() {
if (this.unsubscribe) {
this.unsubscribe();
this.unsubscribe = null;
this.listeners.clear();
this.listeners = nullListeners; }}}Copy the code
Some code is omitted, and the Subscription class basically creates an object that can both listen and subscribe
Then follow the process step by step analysis, first look at the Provider to implement what
Provider
// components/Provider.js
function Provider({ store, context, children }) {
// useMemo returns only when store changes
const contextValue = useMemo((a)= > {
const subscription = new Subscription(store);
// Notifies a child refresh of this subscription
subscription.onStateChange = subscription.notifyNestedSubs;
return {
store,
// Pass this subscription into the context for child subscriptions
subscription
};
}, [store]);
// Cache the last state
const previousState = useMemo((a)= > store.getState(), [store]);
useEffect((a)= > {
const { subscription } = contextValue;
// In this case, the reudx store subscribe event
subscription.trySubscribe();
if(previousState ! == store.getState()) { subscription.notifyNestedSubs(); }return (a)= > {
subscription.tryUnsubscribe();
subscription.onStateChange = null;
};
}, [contextValue, previousState, store]);
// The context passed in or the react-redux built-in
const Context = context || ReactReduxContext;
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}
Copy the code
A Provider is a relatively simple component that does two things
- To subscribe to
redux
thesubscribe()
The event - will
Subscription
Examples of the incomingContext
Facilitate subsubscriptions
Next, look at the core CONNECT component
connect
// connect.js
// Iterate over the function, passing arg as an argument, and return if there is a result
function match(arg, factories, name) {
for (let i = factories.length - 1; i >= 0; i--) {
const result = factories[i](arg);
if (result) return result;
}
 // ... error
}
// ...
export function createConnect({
//The default value connectHOC = connectAdvanced mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {}) {
return function connect(
mapStateToProps,
mapDispatchToProps,
mergeProps,
{
pure = true,
//. Omit some arguments} = {}) {
const initMapStateToProps = match(
mapStateToProps,
mapStateToPropsFactories,
"mapStateToProps"
);
const initMapDispatchToProps = match(
mapDispatchToProps,
mapDispatchToPropsFactories,
"mapDispatchToProps"
);
const initMergeProps = match(mergeProps, mergePropsFactories, "mergeProps");
return connectHOC(selectorFactory, {
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
/ /... Some options are omitted
});
};
}
export default /*#__PURE__*/ createConnect();
Copy the code
Connect() is actually created by createConnect() by default, although we can also call createConnect() to create a custom connect(), but it is rarely used
As you can see, the mapStateToProps, mapDispatchToProps, and mergeProps passed in are actually wrapped by a match() function
Take for example mapStateToPropsFactories defaultMapStateToPropsFactories’s here
mapStateToPropsFactories
// 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((a)= > ({})) : undefined;
}
export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing];
Copy the code
If we pass in mapStateToProps, we call wrapMapToPropsConstant to create a default method
If passed, wrapMapToPropsFunc will be called to wrap our method around whether our method needs to rely on props
wrapMapToProps
// wrapMapToProps.js
// ...
//
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);
};
// Invoke the props again if the props changes based on the value of dependsOnOwnProps
// The default is true because detectFactoryAndVerify is used
proxy.dependsOnOwnProps = true;
proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
The detectFactoryAndVerify method is called only once
// The first call is overwritten by the mapToProps we passed in
proxy.mapToProps = mapToProps;
// This checks whether the function depends on props
// getDependsOnOwnProps() specifies the number of parameters to the function. If the parameter is 2, return true
proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps);
// This value is returned by the mapToProps we passed in
let props = proxy(stateOrDispatch, ownProps);
// props is a function (); // Props is a function ()
// https://react-redux.js.org/api/connect#factory-functions
if (typeof props === "function") {
proxy.mapToProps = props;
proxy.dependsOnOwnProps = getDependsOnOwnProps(props);
props = proxy(stateOrDispatch, ownProps);
}
return props;
};
return proxy;
};
}
Copy the code
SelectorFactory = mapStateToProps = mapDispatchToProps = mergeProps = mapStateToProps = mapDispatchToProps = mergeProps = mapStateToProps = mapDispatchToProps
Let’s look at the implementation of selectorFactory
selectorFactory
// selectorFactory.js
// If pure is false, the we all mapStateToProps method is called each time to get the new data
export function impureFinalPropsSelectorFactory(mapStateToProps, mapDispatchToProps, mergeProps, dispatch) {
return function impureFinalPropsSelector(state, ownProps) {
return mergeProps(
mapStateToProps(state, ownProps),
mapDispatchToProps(dispatch, ownProps),
ownProps
);
};
}
// When pure is true, it checks whether the values are the same
export function pureFinalPropsSelectorFactory(mapStateToProps, mapDispatchToProps, mergeProps, dispatch, { areStatesEqual, areOwnPropsEqual, areStatePropsEqual }) {
// Run at least once
let hasRunAtLeastOnce = false;
// Save for comparison
let state;
let ownProps;
let stateProps;
let dispatchProps;
let mergedProps;
The first call to Selector initializes and stores all the values for later comparisons
function handleFirstCall(firstState, firstOwnProps) {
state = firstState;
ownProps = firstOwnProps;
stateProps = mapStateToProps(state, ownProps);
dispatchProps = mapDispatchToProps(dispatch, ownProps);
mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
hasRunAtLeastOnce = true;
// all returns are mergedProps
return mergedProps;
}
function handleNewPropsAndNewState() {
// re-calculate the props generated by the redux store
stateProps = mapStateToProps(state, ownProps);
// If the mapDispatchToProps needs to be changed against the props, it needs to be recalculated
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps);
// Merge the redux props and dispatch props with the props of the passed component
mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
return mergedProps;
}
function handleNewProps() {
// If mapStateToProps needs to get the props of the component, it needs to recalculate
if (mapStateToProps.dependsOnOwnProps)
stateProps = mapStateToProps(state, ownProps);
// If mapDispatchToProps needs to get the props of the component, it needs to recalculate
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps);
// merge returns
mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
return mergedProps;
}
function handleNewState() {
const nextStateProps = mapStateToProps(state, ownProps);
conststatePropsChanged = ! areStatePropsEqual(nextStateProps, stateProps); stateProps = nextStateProps;// Merge only when changes are made
if (statePropsChanged)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
return mergedProps;
}
function handleSubsequentCalls(nextState, nextOwnProps) {
constpropsChanged = ! areOwnPropsEqual(nextOwnProps, ownProps);conststateChanged = ! areStatesEqual(nextState, state); state = nextState; ownProps = nextOwnProps;// If both props and state change
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);
};
}
// The final function returned
export default function finalPropsSelectorFactory(dispatch, { initMapStateToProps, initMapDispatchToProps, initMergeProps, ... options }) {
// The function passed in is all wrapMapToPropsFunc rewrapped in wrapMapToProps.
const mapStateToProps = initMapStateToProps(dispatch, options);
const mapDispatchToProps = initMapDispatchToProps(dispatch, options);
const mergeProps = initMergeProps(dispatch, options);
if(process.env.NODE_ENV ! = ="production") {
verifySubselectors(
mapStateToProps,
mapDispatchToProps,
mergeProps,
options.displayName
);
}
/ / call pure light contrast pureFinalPropsSelectorFactory, inside to compare whether need to update
const selectorFactory = options.pure
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory;
/ / if it is pure in return is pureFinalPropsSelectorFactory pureFinalPropsSelector function
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
);
}
Copy the code
The current process, knowing that connect() is called, wraps our function to determine whether it depends on props, and then the selectorFactory call will determine whether we need to call our function again based on whether the result has changed
Now let’s look at the implementation of the core higher-order function connectAdvanced
connectAdvanced
import hoistStatics from "hoist-non-react-statics";
import React, { useContext, useMemo, useRef, useReducer } from "react";
import { isValidElementType, isContextConsumer } from "react-is";
import Subscription from ".. /utils/Subscription";
import { useIsomorphicLayoutEffect } from ".. /utils/useIsomorphicLayoutEffect";
import { ReactReduxContext } from "./Context";
// Use the initial value of useReducer
const EMPTY_ARRAY = [];
// The value that the component is not subscribed to
const NO_SUBSCRIPTION_ARRAY = [null.null];
/ / useReducer reducer
function storeStateUpdatesReducer(state, action) {
const [, updateCount] = state;
return [action.payload, updateCount + 1];
}
function useIsomorphicLayoutEffectWithArgs(effectFunc, effectArgs, dependencies) {
useIsomorphicLayoutEffect((a)= >effectFunc(... effectArgs), dependencies); }function captureWrapperProps(lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, actualChildProps, childPropsFromStoreUpdate, notifyNestedSubs) {
// Save it for the next comparison
lastWrapperProps.current = wrapperProps;
lastChildProps.current = actualChildProps;
renderIsScheduled.current = false;
// If the update comes from store, clear the reference and notify the child update
if (childPropsFromStoreUpdate.current) {
childPropsFromStoreUpdate.current = null; notifyNestedSubs(); }}function subscribeUpdates(
//Whether need to update the shouldHandleStateChanges, store,//Subscription,//Connect the selector childPropsSelector,//The last time the component was passed as props lastWrapperProps,//Props includes component props, store props, dispatch props lastChildProps, renderIsScheduled, childPropsFromStoreUpdate, notifyNestedSubs, forceComponentUpdateDispatch) {
// No update required
if(! shouldHandleStateChanges)return;
let didUnsubscribe = false;
let lastThrownError = null;
// This callback is run whenever a store subscription update is delivered to this component
const checkForUpdates = (a)= > {
if (didUnsubscribe) {
// Redux cannot guarantee cancellation before the next dispatch
return;
}
/ / the new state
const latestStoreState = store.getState();
let newChildProps, error;
try {
// Get the new child props
newChildProps = childPropsSelector(
latestStoreState,
lastWrapperProps.current
);
} catch (e) {
error = e;
lastThrownError = e;
}
if(! error) { lastThrownError =null;
}
// If the child props is not changed, do nothing
if (newChildProps === lastChildProps.current) {
// Even if you have not changed, you should notify your subscribing children to check for updates
if (!renderIsScheduled.current) {
notifyNestedSubs();
}
} else {
// Save the new child props, use ref instead of useState/useReducer because we need a way to determine if the value has been processed
// If we use useState/useReducer, we cannot clear the value without forcing an update, which is not what we want
lastChildProps.current = newChildProps;
childPropsFromStoreUpdate.current = newChildProps;
renderIsScheduled.current = true;
// If the Child props changes or catches an error, the Wrapper Component needs to be re-rendered
forceComponentUpdateDispatch({
type: "STORE_UPDATED".payload: { error, }, }); }};// Actually subscribes to the nearest parent or store
subscription.onStateChange = checkForUpdates;
/ / subscribe
subscription.trySubscribe();
checkForUpdates();
/ / unsubscribe
const unsubscribeWrapper = (a)= > {
didUnsubscribe = true;
subscription.tryUnsubscribe();
subscription.onStateChange = null;
if (lastThrownError) {
throwlastThrownError; }};return unsubscribeWrapper;
}
// the useReducer is lazy initialized
const initStateUpdates = (a)= > [null.0];
export default function connectAdvanced(
selectorFactory,
{
//This function calculates HOC's displayName by wrapped Component's displayName//May be used by wrapper functions such as connect() covergetDisplayName = name= > `ConnectAdvanced(${name}`, / / inerror messagesIn the showmethodName="connectAdvanced". / /REMOVED
renderCountProp = undefined, / /falsewhendispatchComponents are not updatedshouldHandleStateChanges = true, / /REMOVED
storeKey="store". / /REMOVED
withRef = false// Whether to passref
forwardRef = false, // usedcontext consumer
context = ReactReduxContext, // Other values will be passed toselectorFactory.connectOptions
} = {{})// ...
// context
const Context = context;
// The function that is actually called by connect, WrappedComponent is the component passed in
return function wrapWithConnect(WrappedComponent) {
// Pass in the name of the component, which can be seen on the React plugin
const wrappedComponentName =
WrappedComponent.displayName || WrappedComponent.name || "Component";
const displayName = getDisplayName(wrappedComponentName);
// Pass to selectorFactory
constselectorFactoryOptions = { ... connectOptions, getDisplayName, methodName, renderCountProp, shouldHandleStateChanges, storeKey, displayName, wrappedComponentName, WrappedComponent, };// Whether to cache values
const { pure } = connectOptions;
// Encapsulate selectorFactory
function createChildSelector(store) {
return selectorFactory(store.dispatch, selectorFactoryOptions);
}
// Use useMemo in pure mode, otherwise call back directly
const usePureOnlyMemo = pure ? useMemo : callback= > callback();
// This is the component rendered on the page
function ConnectFunction(props) {
const [propsContext, forwardedRef, wrapperProps] = useMemo((a)= > {
// Distinguish between the values passed in for props and control behavior (forward ref, substitute context instance)
const{ forwardedRef, ... wrapperProps } = props;return [props.context, forwardedRef, wrapperProps];
}, [props]);
// The react redux context is passed in by the component
const ContextToUse = useMemo((a)= > {
// Should the cache use the native context or the user-passed context
returnpropsContext && propsContext.Consumer && isContextConsumer(<propsContext.Consumer />) ? propsContext : Context; }, [propsContext, Context]); // Store and subscription const contextValue = useContext(ContextToUse); // Store must exist in props or context, 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(() => {// createChildSelector takes store as an argument, and re-creates return when store changes createChildSelector(store); }, [store]); Const [subscription, notifyNestedSubs] = useMemo(() => {// If (! shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY; /* [null, null] */ / If the component store is from props, Const subscription = new subscription (store, / / contextValue. Subscription this value, the root in the Provider's store subscription, The rest is parent subscription, because every time connect returns a Provider outside the component, it uses a new value Store subscription // <Test5 /> // Test4 subscription // <Test6 /> didStoreComeFromProps ? null : contextValue.subscription ); / / prevent in the notify loop component is unmount to const notifyNestedSubs = subscription. NotifyNestedSubs. Bind (subscription); return [subscription, notifyNestedSubs]; }, [store, didStoreComeFromProps, contextValue]); Const overriddenContextValue = useMemo(() const overriddenContextValue = useMemo If (didStoreComeFromProps) {// If (didStoreComeFromProps) {// If (didStoreComeFromProps) {// If (didStoreComeFromProps) {// If (didStoreComeFromProps) {// If (didStoreComeFromProps) {// If (didStoreComeFromProps) {// If (didStoreComeFromProps) {return contextValue; } // Otherwise, put the current component's subscription into the context to ensure that the child component does not update until the current component is updated. Return {... contextValue, subscription, }; }, [didStoreComeFromProps, contextValue, subscription]); / / when we need redux store update forcing packaging components/update / * * components to render is normally because call forceComponentUpdateDispatch, And call this is in the event of subscription * * const [[previousStateUpdateResult], forceComponentUpdateDispatch, ] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates); / / capture errors produced by updating the if (previousStateUpdateResult && previousStateUpdateResult. Error) {throw previousStateUpdateResult. Error; Const lastChildProps = useRef();} // The function is set to const lastChildProps = useRef(); // Const lastWrapperProps = useRef(wrapperProps); const childPropsFromStoreUpdate = useRef(); Const renderIsScheduled = useRef(false); Const actualChildProps = usePureOnlyMemo(() => {// This render may be triggered by the redux store update If we get a new child props, and the same parent props, we know we should use the new child props. However, if the parent passes in a new props, // So, if the parent props is the same as last time, We will use new props from store to update the if (childPropsFromStoreUpdate. Current && wrapperProps = = = lastWrapperProps. Current) {return childPropsFromStoreUpdate.current; } return childPropsSelector(store.getState(), wrapperProps); / /, mainly because of the change of previousStateUpdateResult will recalculate actualChildProps}, [store, previousStateUpdateResult, wrapperProps]); / / useIsomorphicLayoutEffectWithArgs is based on the server or the browser to invoke useEffect or useLayoutEffect / / here is mainly the initialization value, Used for comparison after the update / / and calls itself notifyNestedSubs, let the child components also update useIsomorphicLayoutEffectWithArgs (captureWrapperProps, [ lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, actualChildProps, childPropsFromStoreUpdate, notifyNestedSubs, ]); / / in store or subscription change to subscribe / / here mainly binding subscription event useIsomorphicLayoutEffectWithArgs (subscribeUpdates, [ shouldHandleStateChanges, store, subscription, childPropsSelector, lastWrapperProps, lastChildProps, renderIsScheduled, childPropsFromStoreUpdate, notifyNestedSubs, forceComponentUpdateDispatch, ], [store, subscription, childPropsSelector] ); Const renderedWrappedComponent = useMemo(() => <WrappedComponent {... actualChildProps} ref={forwardedRef} />, [forwardedRef, WrappedComponent, actualChildProps] ); Const renderedChild = useMemo(() => {if (shouldHandleStateChanges) {// If the component subscribed to store updates, Return (<ContextToUse.Provider value={overriddenContextValue}>) {renderedWrappedComponent} </ContextToUse.Provider> ); } return renderedWrappedComponent; }, [ContextToUse, renderedWrappedComponent, overriddenContextValue]); return renderedChild; } // Use react. memo to optimize const Connect = pure? React.memo(ConnectFunction) : ConnectFunction; Connect.WrappedComponent = WrappedComponent; Connect.displayName = displayName; // If the forwardRef is on, If (forwardRef) {const forwardRef = forwardRef(function forwardConnectRef(props, ref ) { return <Connect {... props} forwardedRef={ref} />; }); forwarded.displayName = displayName; forwarded.WrappedComponent = WrappedComponent; Return hoistStatics(ForwardedwrappedComponent); // Copy the static method and return hoistStatics(ForwardedWrappedComponent); } return hoistStatics(Connect, WrappedComponent); }; }Copy the code
(Why is the code highlighting messed up? If someone really reads it, they can read my blog, the code highlighting will be better.)
After reading the overall Connection Advanced, THERE is still one question I don’t understand
- Why do components subscribe hierarchically from the top down rather than directly
store
thesubscribe
?
Because in useSelector, hooks do not pass context, so they subscribe to providers. There is no subscription tier for Connect ()
I hope someone big can answer this little puzzle
The entire React-Redux source code is quite convoluted, so if you have any questions, you can talk to each other
From the first day a face meng forced to the seventh day basic understand, even wrote a simple version, or very happy
Finally, I wish you all good health and smooth work!
Welcome to pay attention to my public number ~