Welcome to the Public Number: One front end at a time, sharing what I understand on an irregular basis
Write before
While I was reading React-Redux source code, it was natural for me to go online and find some reference articles, but I found that none of them were very thorough.
Many times the API is told one by one in a flat, straight-line way, and only what a line of code does, without a clear explanation of why it does so in combination with scenarios and usage, and the source code itself is abstract.
The call relationships between functions are not very well organized, and the end result is that the more you look at them, the more confused they become.This time I'll try a different interpretation, starting with the most common usage.
Combine usage, ask questions, and take questions to see how they are implemented in the source code so that you can work with everyone to gradually clarify the operating mechanism of React-Redux.
The article took more than a week to finish, after a rough look at the source code, and then read and write.There are not too few sources. I try to sort out the structure in the most understandable way and try to explain the principles in a simple way.
However, the complexity of the code structure cannot be overwhelmed, and many places still need time to think about how functions are called and how they are used.The article is a bit long, you can see the last one is true love~
Limited level, inevitably there are inadequate explanations or errors, and we hope you can help point them out. Thank you.
Application of React-Redux in Project
Here, I'm going to default that you're already using Redux, which provides a global object (store) to manage state for our applications.
So how do you apply Redux to React?Consider that our ultimate goal is to achieve unified management of communication and state across components at different levels.So you can use the Context feature.
- Create a Provider and pass the store into the Provider as the value of the current context so that the component can get the Redux store from the context
- store subscribes to the unified logic for component updates
- When a component needs to update its data, it needs to call store.dispatch to dispatch the action, which triggers the update of the subscription
- Use store.getState() to get data when the component gets data
And all this needs to be done manually by yourself, React-Redux encapsulates the top.Let's take a look at the use of React-Redux in a piece of code:
First, on the outermost application of React, package the Provider, which is the component provided by React-Redux, and what you do here is the equivalent of the first step above
import React from 'react' import { Provider } from 'react-redux' import { createStore } from 'redux' const reducer = (state, actions) => { ... } const store = createStore(reducer) ... class RootApp extends React.Component { render() { // store is passed into Provider here return <Provider store={store}> <App/> </Provider> } }
The subscription in Step 2 has been implemented in Provider and connect, respectively
Look at the subcomponents in the application.If you need to pick up data from the store or update the store data (equivalent to steps 3 and 4 above),
The components need to be wrapped in a connect ion:
import React from 'react' import { connect } from '../../react-redux-src' import { increaseAction, decreaseAction } from '../../actions/counter' import { Button } from 'antd' class Child extends React.Component { render() { const { increaseAction, decreaseAction, num } = this.props return <div> {num} <Button onClick={() => increaseAction()}>increase</Button> <Button onClick={() => decreaseAction()}>reduce</Button> </div> } } const mapStateToProps = (state, ownProps) => { const { counter } = state return { num: counter.num } } const mapDispatchToProps = (dispatch, ownProps) => { return { increaseAction: () => dispatch({ type: INCREASE }), decreaseAction: () => dispatch({ type: DECREASE }) } } export default connect(mapStateToProps, mapDispatchToProps)(Child)
The mapStateToProps is used to establish a mapping relationship between the component and the state stored in the store. It is a function. The first parameter is state, which is the top level data stored in redux, and the second parameter is props of the component itself.Returns an object whose field is the value that the component needs to get from the store.
MappDispatchToProps is used to establish a mapping relationship between components and store.dispatch.It can be an object or a function.
When it is a function, the first parameter is dispatch, and the second parameter is the props of the component itself.
The object form of mapDispatchToProps is as follows:
const mapDispatchToProps = { increaseAction() { return dispatch => dispatch({ type: INCREASE }) }, decreaseAction() { return dispatch => dispatch({ type: DECREASE }) } }
When mapStateToProps is not passed, it does not cause component UI updates when store s change.
When mapDispatchToProps is not passed, dispatch is injected into the props of the component by default.
Above, if mapStateToProps or mapDispatchToProps passed ownProps, both functions will be called when the props of the component itself change.
What React-Redux did
Let's first draw a conclusion about what React-Redux does:
- Provides a Subscrption class to implement logic for subscription updates
- Provider s are provided to pass stores into providers so that lower-level components can retrieve stores from context s or props; changes to stores are subscribed to so that providers themselves can be updated as stores change
- Provides a selector that takes functions (or directly dispatch es) of stat and dispacth action s in the store or the component's own props and selects from them the values required by the component as return values
-
There are two main things you can do to provide high-level connect s components:
- Execute selector, get values to inject into the component, and inject them into the props of the component
- Subscribe to changes in props, responsible for updating components as props change
How to do it
With the above conclusion, but I think everyone is curious about how to achieve it. The above work is done in collaboration. The final appearance is reflected in the following issues:
- How Provider puts store s into context s
- How to inject a state and dispatch (or a function that calls dispatch) from the store into the props of a component
- We all know that in Redux, store changes and UI updates can be achieved by subscribing to a function that updates a page through store.subscribe(), and how React-Redux changes stores and connect ed components update
Next, analyze the source code one by one with these questions.
How Provider puts store s into context s
Start with Provider components, not much code, directly up source
class Provider extends Component { constructor(props) { super(props) // Remove store from props const { store } = props this.notifySubscribers = this.notifySubscribers.bind(this) // Declare a Subscription instance.Subscriptions, listening for state changes to execute listener s, are all implemented by instances. const subscription = new Subscription(store) // Bind monitoring, notify subscribers to update pages when state changes subscription.onStateChange = this.notifySubscribers // Put store and subscription into the state, which will be used as the value of the context later this.state = { store, subscription } // Getting the state in the current store, as the last state, will be done after the component is mounted. // Update Provider component if inconsistent with store new state this.previousState = store.getState() } componentDidMount() { this._isMounted = true // Subscribe to updates after the component has been mounted.As for how to subscribe, the Subscription class is described below. // Understand that you need to subscribe to the update function initially to update the Provider component when the state changes this.state.subscription.trySubscribe() // If the state s in the store s before and after have changed, then go and update the Provider component if (this.previousState !== this.props.store.getState()) { this.state.subscription.notifyNestedSubs() } } componentWillUnmount() { // Unsubscribe when component is uninstalled if (this.unsubscribe) this.unsubscribe() this.state.subscription.tryUnsubscribe() this._isMounted = false } componentDidUpdate(prevProps) { // When the component is updated, check to see if the current store is consistent with the previous store. If it is not, you should make changes based on the new data. // It is no longer meaningful to make changes to the original data, so the Subscription instance will be redeclared after unsubscribing. // Bind listening, set state to new data if (this.props.store !== prevProps.store) { this.state.subscription.tryUnsubscribe() const subscription = new Subscription(this.props.store) subscription.onStateChange = this.notifySubscribers this.setState({ store: this.props.store, subscription }) } } notifySubscribers() { // notifyNestedSubs() actually notifies listener to execute, which is to update the UI this.state.subscription.notifyNestedSubs() } render() { const Context = this.props.context || ReactReduxContext // Pass this.state as the value of the context return ( <Context.Provider value={this.state}> {this.props.children} </Context.Provider> ) } }
So look at this in conjunction with the code: It's easy to understand how Provider puts store s into context s.
The main function of Provider is to get the store we passed in from props and send it down to the lower components as one of the values of the context.
However, once the store changes, the Provider has to react to it to ensure that the latest store is always in the context.So subscriptions are used here to update.The Subscription class is naturally introduced, and through its instances onStateChange listens for an event that updates the UI
On this.notifySubscribers:
subscription.onStateChange = this.notifySubscribers
After the component is mounted, subscribe to the update, and what you subscribe to here depends on the implementation of Subscription.The first conclusion here is that the onStateChange subscription is essentially an onStateChange subscription, and the function that implements the subscription is trySubscribe within the Subscription class
this.state.subscription.trySubscribe()
Then, if the States before and after are different, go and notify the subscriber to update, onStateChange will be executed, and the Provider component will be updated.When the update is complete (componentDidUpdate),
It compares whether the stores before and after are the same, and if they are different, uses the new store as the value of the context, unsubscribes and re-subscribes to a new Subscription instance.The data guaranteed to be up to date.
So with all this said, it's actually just an update of the Provider component, not an internal update mechanism of a connect ed component.I guess there should be one reason to think about the potential for providers to be nested, so there is this practice of retrieving data and re-subscribing after providers are updated so that the context s passed to subcomponents are up-to-date.
Subscription
We have found that the Provider component is updated by a method in the Subscription class, and that the update of the connect higher-order component, which we will talk about later, is also implemented by it. Subscription is the core mechanism for React-Redux to implement subscription updates.
import { getBatch } from './batch' const CLEARED = null const nullListeners = { notify() {} } function createListenerCollection() { const batch = getBatch() let current = [] let next = [] return { clear() { // Empty next and current next = CLEARED current = CLEARED }, notify() { // Assign next to current and listeners, where next, current, listeners are actually queues of subscribed update functions const listeners = (current = next) // Batch listeners batch(() => { for (let i = 0; i < listeners.length; i++) { // Perform update functions, which are the most fundamental principle for triggering UI updates listeners[i]() } }) }, get() { return next }, subscribe(listener) { let isSubscribed = true // Copy the current and assign it to next, then push listener to next if (next === current) next = current.slice() next.push(listener) return function unsubscribe() { if (!isSubscribed || current === CLEARED) return isSubscribed = false // Ultimately, a unsubscribe function is returned to clean up useless listener s on the next round if (next === current) next = current.slice() next.splice(next.indexOf(listener), 1) } } } } export default class Subscription { constructor(store, parentSub) { // Get a store to subscribe through this.store = store // Get a subscription instance from the parent, mainly one you might use when connect ing this.parentSub = parentSub this.unsubscribe = null this.listeners = nullListeners this.handleChangeWrapper = this.handleChangeWrapper.bind(this) } addNestedSub(listener) { this.trySubscribe() // Since this is called by parentSubs, listener s are also subscribed to parentSubs, which is the subscription obtained from the Provider return this.listeners.subscribe(listener) } notifyNestedSubs() { // Notify listeners to execute this.listeners.notify() } handleChangeWrapper() { if (this.onStateChange) { // onStateChange is assigned a different update function when it is externally instantiated as a subcription instance, in Provider and connect, respectively. // Since the function you just subscribed to is handleChangeWrapper, it is also equivalent to listener.So when the state changes, listener executes, onStateChange executes this.onStateChange() } } isSubscribed() { return Boolean(this.unsubscribe) } trySubscribe() { if (!this.unsubscribe) { // ParentSubis actually a subcription instance // This judges the value of this.unsubscribe after it has been assigned, which essentially means whether parentSub is there, and assigns it to this.unsubscribe by the way. // If parentSub is not passed, use store subscription, otherwise call parentSub.addNestedSub and use React-Redux's own subscription logic.This is explained in the explanation below the code this.unsubscribe = this.parentSub ? this.parentSub.addNestedSub(this.handleChangeWrapper) : this.store.subscribe(this.handleChangeWrapper) // Create a listener collection this.listeners = createListenerCollection() } } tryUnsubscribe() { // unsubscribe if (this.unsubscribe) { this.unsubscribe() this.unsubscribe = null this.listeners.clear() this.listeners = nullListeners } } }
Subscription links the work of updating a page to changes in its state, specifically listener (the method that triggers page updates, in this case handleChangeWrapper), which, through the trySubscribe method, is subscribed to within the store or Subscription as appropriate.Place in the listeners array, and when the state changes, the listeners loop executes each listener, triggering page updates.
Let's talk about whether trySubscribe uses store subscriptions directly or calls addNestedSub to implement internal subscriptions, depending on the circumstances.Because there may be multiple stores in an application, the judgement here is to have different stores subscribe to their listener s without interruption.
How to inject state and dispatch into components
After injecting store s from the top level of the application, it's time to consider how to inject States and dispatch es into the component.
The normal order must be to get to the store first, then execute the two functions separately in some way, passing in the state and dispatch in the store, along with the props of the component itself as parameters to mapStateToProps and mapDispatchToProps, so that we can get these values in both functions.Their return values are then injected into the props of the component.
Speaking of this, you have to come up with a concept: selector.The props that are eventually injected into the component are the return values of selectors generated by the selectorFactory function, so that is, mapStateToProps and mapDispatchToProps are essentially selectors.
The generation process is in the core function connectAdvanced of connect, at which point you can get the store in the current context and use the store passed into the selectorFactory to generate the selector in the form of
function selector(stateOrDispatch, ownProps) { ... return props }
It can be seen that selector is equivalent to mapStateToProps or mapDispatchToProps, and the return value of selector is injected into the component as props.
From mapToProps to selector
The Title mapToProps refers generally to mapStateToProps, mapDispatchToProps, mergeProps
In combination with everyday use, we know that our components don't get States and dispatch es until they are wrapped by connects, so with the above conclusion, we comb the selector's mechanism separately and see the source code of connects first:
export function createConnect({ connectHOC = connectAdvanced, // ConneAdvanced function is the core of connect mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {}) { return function connect( mapStateToProps, mapDispatchToProps, mergeProps, {...options} = {} ) { // Initialize all of our incoming mapStateToProps, mapDispatchToProps, mergeProps const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps') const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps') const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps') // Returns the call to the connectHOC function, whose interior is the core of the connection return connectHOC(selectorFactory, { initMapStateToProps, initMapDispatchToProps, initMergeProps, pure, ... }) } } export default createConnect()
Connect is actually createConnect, and createConnect simply returns a connect function, which returns the call to connectHOC (that is, the call to connectAdvanced), and then proceeds. The call to connectAdvanced eventually returns a wrapWithConnect higher-order component, the parameter of which is what we pass inComponents of.So there's the usual use of connect:
connect(mapStateToProps, mapDispatchToProps)(Component)
You should note that mapStateToProps, mapDispatchToProps, mergeProps are all initialized once in the connect function. Why do you want to initialize them instead of using them directly?With questions, let's look down.
Initialize the selector process
First look at the code, mainly initMapStateToProps and initMapDispatchToProps, to see what this code means.
const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps') const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps') const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
mapStateToPropsFactories and mapDispatchToPropsFactories are arrays of functions, each of which receives a parameter, either mapStateToProps or mapDispatchToProps.The match function acts as an array of looping functions, either mapStateToProps or mapDispatchToProps being executed as an input to each function and assigned to the left when the return value of the function is not false.Take a look at the match function:
function match(arg, factories, name) { // Loop through factories, where factories are the array of processing functions exposed in the mapStateToProps and mapDisPatchToProps files for (let i = factories.length - 1; i >= 0; i--) { // arg is also mapStateToProps or mapDispatchToProps // This is equivalent to passing the Star of each function in the array and passing in our mapToProps function as a parameter const result = factories[i](arg) if (result) return result } }
The match loop is an array of functions, so let's look at these two arrays, mapStateToPropsFactories and mapDispatchToPropsFactories:
(The whenMapStateToPropsIsFunction function in the lower source code is explained later)
-
mapStateToPropsFactories
-
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps' // Call wrapMapToPropsFunc when mapStateToProps is a function export function whenMapStateToPropsIsFunction(mapStateToProps) { return typeof mapStateToProps === 'function' ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps') : undefined } // Call wrapMapToPropsConstant when mapStateToProps is not passed export function whenMapStateToPropsIsMissing(mapStateToProps) { return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined } export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]
In fact, let both whenMapStateToPropsIsFunction and whenMapStateToPropsIsMissing execute mapStateToProps once, and then choose the function with the result of execution to assign to initMapStateToProps based on the situation of the incoming mapStateToProps.
Take a separate look at whenMapStateToPropsIsMissing
export function wrapMapToPropsConstant(getConstant) { return function initConstantSelector(dispatch, options) { const constant = getConstant(dispatch, options) function constantSelector() { return constant } constantSelector.dependsOnOwnProps = false return constantSelector } }
wrapMapToPropsConstant returns a function that receives the parameter we passed in () => ({}), which calls the parametric function internally and assigns a constant to the constantSelector.
This constant is essentially the selector generated when we do not pass mapStateToProps, which returns an empty object and therefore does not accept any state from the store.You can also see constantSelector.dependsOnOwnProps = false, indicating that the return value is independent of the props received by the connect or higher-order component.
-
-
mapDispatchToPropsFactories
-
import { bindActionCreators } from '../../redux-src' import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps' export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) { return typeof mapDispatchToProps === 'function' ? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps') : undefined } // When mapDispatchToProps is not passed, dispatch is injected into the component by default export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) { return !mapDispatchToProps ? wrapMapToPropsConstant(dispatch => ({ dispatch })) : undefined } // When the incoming mapDispatchToProps is an object, use bindActionCreators for processing as detailed in redux/bindActionCreators.js export function whenMapDispatchToPropsIsObject(mapDispatchToProps) { return mapDispatchToProps && typeof mapDispatchToProps === 'object' ? wrapMapToPropsConstant(dispatch => bindActionCreators(mapDispatchToProps, dispatch)) : undefined } export default [ whenMapDispatchToPropsIsFunction, whenMapDispatchToPropsIsMissing, whenMapDispatchToPropsIsObject ]
When mapDispatchToProps is not passed, when MapDispatchToPropsIsMissing is called, constantSelector returns only one dispatch, so dispatches can only be received in the component.
When the incoming mapDispatchToProps is an object, it also calls wrapMapToPropsConstant, where, according to the previous understanding, the properties injected into the component are
The result of execution of bindActionCreators(mapDispatchToProps, dispatch).
-
Now let's look at the whenMapStateToPropsIsFunction function.It is called when both mapDispatchToProps and mapStateToProps are functions and the implementation is complex.Only mapStateToProps is used to illustrate this.
Reminder: the following mapToProps refer to mapDispatchToProps or mapStateToProps
// Determine whether a component should depend on its props based on the number of arguments to the mapStateToProps function export function getDependsOnOwnProps(mapToProps) { return mapToProps.dependsOnOwnProps !== null && mapToProps.dependsOnOwnProps !== undefined ? Boolean(mapToProps.dependsOnOwnProps) : mapToProps.length !== 1 } export function wrapMapToPropsFunc(mapToProps, methodName) { // The final wrapMapToPropsFunc returns a proxy function, which is returned in the selectorFactory function // Is called within finalPropsSelectorFactory and assigned to another variable. // This proxy function is executed in the selectorFactory to generate the final selector return function initProxySelector(dispatch, { displayName }) { const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) { // Depending on whether the component relies on its props, it decides what parameters to pass when calling return proxy.dependsOnOwnProps ? proxy.mapToProps(stateOrDispatch, ownProps) : proxy.mapToProps(stateOrDispatch) } proxy.dependsOnOwnProps = true proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) { // Assign proxy.mapToProps to the mapToProps we passed in proxy.mapToProps = mapToProps // Determines whether ownProps need to be injected into a component based on whether the component passes in props that the component itself receives from the parent component. // Ultimately it will be used to implement the props change of the component itself, and it will also call the effect of mapToProps proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps) // Go ahead and execute proxy, when proxy.mapToProps has been assigned to the mapToProps function that we passed in, // So props are assigned to the return value of the incoming mapToProps let props = proxy(stateOrDispatch, ownProps) if (typeof props === 'function') { // If the return value is a function, then execute the function and pass in the state or dispatch in the store, as well as ownProps 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 } }
WapMapToPropsFunc actually returns the initProxySelector function, and the execution result of initProxySelector is a proxy, which can be understood as proxying incoming data (state or dispatch, ownProps) to our incoming mapToProps function.The result of proxy execution is proxy.mapToProps, which is essentially selector.
When the page initialization executes, dependsOnOwnProps is true, so proxy.mapToProps(stateOrDispatch, ownProps), also known as detectFactoryAndVerify, is executed.In subsequent executions, the proxy's mapToProps are assigned to the mapStateToProps or mapDispatchToProps we pass in to connect, and then whether the component should depend on its own props is assigned to dependsOnOwnProps as appropriate.(Note that this variable is used in the selectorFactory function as a basis for whether the component performs the mapToProps function based on its own props changes).
To summarize, what this function essentially does is hang the mapToProps function that we pass in to connect onto proxy.mapToProps, and then mount a dependsOnOwnProps on the proxy to make it easier to distinguish whether a component depends on its own props.Finally, the proxy is also used as the return value of the initProxySelector, so the initialization process is actually referenced by the functions of the initMapStateToProps, initMapDispatchToProps, initMergeProps assigned to them, which are executed followed by the proxy, as to where their three proxys are executed to generate a specific selector for meYou'll see below.
Now, recall our question, why do you want to initialize those three mapToProps functions?The goal is obviously to prepare the function that generates the selector to execute it at the right time and decide whether the selector should respond to changes in ownProps.
Create a selector and inject props into the component
Once you're ready to generate the selector function, you need to execute it and inject its return value into the component as props.First, give a brief overview of the injection process:
- Get the store's state or dispatch, and ownProps
- Execute selector
- Inject the returned value of execution into the component
Next, we need to go backwards from the last step of injection to see how the selector performs.
The injection process occurs in the core function connectAdvanced of connect. Ignore other processes in the function, focus on the injection process, and simply look at the source code
export default function connectAdvanced( selectorFactory, { getDisplayName = name => `ConnectAdvanced(${name})`, methodName = 'connectAdvanced', renderCountProp = undefined, shouldHandleStateChanges = true, storeKey = 'store', withRef = false, forwardRef = false, context = ReactReduxContext, ...connectOptions } = {} ) { const Context = context return function wrapWithConnect(WrappedComponent) { // ...other code ignored // selectorFactoryOptions is a series of parameters that contain our initialized mapToProps const selectorFactoryOptions = { ...connectOptions, getDisplayName, methodName, renderCountProp, shouldHandleStateChanges, storeKey, displayName, wrappedComponentName, WrappedComponent } // pure means that the selector is recalculated only when the state or ownProps changes. const { pure } = connectOptions /* createChildSelector Called as createChildSelector(store)(state, ownProps), createChildSelector A call to selectorFactory is returned, and selectorFactory is actually returned internally according to options.pure impureFinalPropsSelectorFactory Or a call to pureFinalPropsSelectorFactory, which requires parameters such as mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options All parameters except dispatch are available from selectorFactoryOptions.The return value of the call is selector.The parameters required by selector are (state, ownprops). So we conclude that createChildSelector(store) is a selector */ function createChildSelector(store) { // Here is the call to finalPropsSelectorFactory in selectorFactory.js (essentially the call to mapToProps we initialized above), passing in dispatch, and options return selectorFactory(store.dispatch, selectorFactoryOptions) } function ConnectFunction(props) { const store = props.store || contextValue.store // Create selector only when store changes // Call childPropsSelector => childPropsSelector (dispatch, options) const childPropsSelector = useMemo(() => { // Recreate this selector whenever the store changes return createChildSelector(store) }, [store]) // ActlChildProps is the props that are ultimately injected into the component, which is the return value of the selector. const actualChildProps = usePureOnlyMemo(() => { return childPropsSelector(store.getState(), wrapperProps) }, [store, previousStateUpdateResult, wrapperProps]) const renderedWrappedComponent = useMemo( // This is where props are injected into the component () => <WrappedComponent {...actualChildProps} />, [forwardedRef, WrappedComponent, actualChildProps] ) } // Last return Out return hoistStatics(Connect, WrappedComponent) }
There is one important thing during the injection process: the selectorFactory.This function is an important part of generating selectors.It serves as an upload and download function, passing the received dispatch, along with the three mapToProps functions, into the processing function (pureFinalPropsSelectorFactory or impureFinalPropsSelectorFactory) inside the selectorFactory, whose execution result is a call to the internal processing function.The result of the internal processing function is that the three selectors (mapStateToProps, mapDispatchToProps, mergeProps)
The result of the merge after execution.This is the props that will eventually be passed to the component
Let's look at the internal implementation of selectorFactory.For clarity, just look at the internal structure
// Merge the execution results of mapStateToProps, mapDispatchToProps, ownProps directly as return values export function impureFinalPropsSelectorFactory(){} export function pureFinalPropsSelectorFactory() { // Called the first time the entire process is initialized function handleFirstCall(firstState, firstOwnProps) {} // Return to new props function handleNewPropsAndNewState() { // Merge the execution results of mapStateToProps, mapDispatchToProps, ownProps as return values } // Return to new props function handleNewProps() { // Merge the execution results of mapStateToProps, mapDispatchToProps, ownProps as return values } // Return to new props function handleNewState() { // Merge the execution results of mapStateToProps, mapDispatchToProps, ownProps as return values } // Subsequent process calls function handleSubsequentCalls(nextState, nextOwnProps) {} return function pureFinalPropsSelector(nextState, nextOwnProps) { // The first rendering, calls handleFirstCall, and subsequent action distributions trigger handleSubsequentCalls return hasRunAtLeastOnce ? handleSubsequentCalls(nextState, nextOwnProps) : handleFirstCall(nextState, nextOwnProps) } } // The finalPropsSelectorFactory function is a selectorFactory function that is called within the connectAdvaced function export default function finalPropsSelectorFactory( dispatch, { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options } ) { const mapStateToProps = initMapStateToProps(dispatch, options) // Here is the Curitized call to the wrapMapToPropsFunc function in wrapMapToProps.js, a modification // Later, mapStateToProps will be called again within the function returned below const mapDispatchToProps = initMapDispatchToProps(dispatch, options) const mergeProps = initMergeProps(dispatch, options) // Depending on whether the pure property is passed in, decide which function to call to generate the selector to calculate the props passed to the component.And assign the matched function to selectorFactory const selectorFactory = options.pure ? pureFinalPropsSelectorFactory // Recalculate props when props or state changes : impureFinalPropsSelectorFactory // Recalculate props directly // Returns a call to selectorFactory return selectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options ) }
It can be seen that the selectorFactory internally determines when a new props will be generated.Let's take a look at the complete source code
export function impureFinalPropsSelectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch ) { // If this function is called, the execution results of the three selector s are merged back directly return function impureFinalPropsSelector(state, ownProps) { return mergeProps( mapStateToProps(state, ownProps), mapDispatchToProps(dispatch, ownProps), ownProps ) } } export function pureFinalPropsSelectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, { areStatesEqual, areOwnPropsEqual, areStatePropsEqual } ) { // Save a variable with a closure to mark whether it is the first execution let hasRunAtLeastOnce = false // The lower variables are used to cache the calculation results let state let ownProps let stateProps let dispatchProps let mergedProps function handleFirstCall(firstState, firstOwnProps) { state = firstState ownProps = firstOwnProps // Here is the call to the proxy function inside the Curitized call to the wrapMapToPropsFunc function in wrapMapToProps.js. stateProps = mapStateToProps(state, ownProps) /* * Your knee is rotten, it's too wrapped * Review proxy: * const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {} * return proxy * */ dispatchProps = mapDispatchToProps(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) hasRunAtLeastOnce = true // Return calculated props return mergedProps } function handleNewPropsAndNewState() { stateProps = mapStateToProps(state, ownProps) // Since the call conditions for this function are ownProps and state, it is necessary to judge dependsOnOwnProps if (mapDispatchToProps.dependsOnOwnProps) dispatchProps = mapDispatchToProps(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } function handleNewProps() { // Decide to recalculate stateProps if you need to rely on the component's own props if (mapStateToProps.dependsOnOwnProps) { stateProps = mapStateToProps(state, ownProps) } // Ditto if (mapDispatchToProps.dependsOnOwnProps) dispatchProps = mapDispatchToProps(dispatch, ownProps) // Integrate the component's own props, dispatchProps, stateProps mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } function handleNewState() { const nextStateProps = mapStateToProps(state, ownProps) const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps) stateProps = nextStateProps // Since handleNewState executes on the premise that pure is true, it is necessary to determine whether the state from the store has changed before or after if (statePropsChanged) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } function handleSubsequentCalls(nextState, nextOwnProps) { const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps) const stateChanged = !areStatesEqual(nextState, state) state = nextState ownProps = nextOwnProps // Depending on the situation, call different functions if (propsChanged && stateChanged) return handleNewPropsAndNewState() // Call handleNewPropsAndNewState() to get the latest props when the component's own props and some state s in the injected store change simultaneously if (propsChanged) return handleNewProps() // Call handleNewProps to get the latest props only when the component's own props change, where props include injected props, the component's own props, and functions within dpspatch if (stateChanged) return handleNewState() // Call handleNewState() to get the latest props only if some state in the injected store changes, where props include injected props, props of the component itself, and functions within dpspatch // If it does not change, go directly back to the previously cached mergedProps, and in all three functions, the data is cached using closure mechanisms return mergedProps } return function pureFinalPropsSelector(nextState, nextOwnProps) { // The first rendering, calls handleFirstCall, and subsequent action distributions trigger handleSubsequentCalls return hasRunAtLeastOnce ? handleSubsequentCalls(nextState, nextOwnProps) : handleFirstCall(nextState, nextOwnProps) } } export default function finalPropsSelectorFactory( dispatch, { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options } ) { const mapStateToProps = initMapStateToProps(dispatch, options) // Here is the Curitized call to the wrapMapToPropsFunc function in wrapMapToProps.js, a modification // Later, mapStateToProps will be called again within the function returned below const mapDispatchToProps = initMapDispatchToProps(dispatch, options) const mergeProps = initMergeProps(dispatch, options) // Verify the mapToProps function and alert you when there are errors if (process.env.NODE_ENV !== 'production') { verifySubselectors( mapStateToProps, mapDispatchToProps, mergeProps, options.displayName ) } // Decide how the new props will be calculated based on whether pure is passed in, defaulting to true const selectorFactory = options.pure ? pureFinalPropsSelectorFactory : impureFinalPropsSelectorFactory return selectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options ) }
So far, we have understood when the mapToProps function was executed.Let's review this section again: How to inject state and dispatch into the component, let's comb it out from the beginning:
Incoming mapToProps
First, when you connect, you pass in mapStateToProps, mapDispatchToProps, mergeProps.Recall the usage of functions that receive state s or dispatch es and ownProps internally, and their return values pass into the props of the component.
Generating selector based on mapToProps
The return values of these functions need to be recalculated based on ownProps, so proxy functions are generated based on these functions. The result of proxy function execution is selector. The dependsOnOwnProps property is mounted on it, so it is only when it is actually executed inside selectorFactory.Basis for when to recalculate.
Pass the results of selector execution into the component as props
This step creates a call to selectorFactory within the connectAdvanced function to pass in the store and the initialized mapToProps function and other configurations.Execute mapToProps (that is, selector) within the selectorFactory, get the returned values, and finally pass them into the component.
Be accomplished
Update mechanism for React-Redux
The React-Redux update mechanism is also a subscription publishing mode.Similar to Redux, call listener to update the page once the state changes.Let's follow this process to capture the key points:
- Update Who?
- What is the update function for subscriptions?
- How can I judge the state change?
Don't look at the code in a hurry. I think it's easier to understand these key issues by describing them in words first, instead of just looking at the code in a fog.
Update Who?
Recall that when you normally use React-Redux, are only components that have been connect ed and passed into mapStateToProps responding to changes in store s?
So what's updated is the component that has been connected, and connect returns connectAdvanced, and connectAdvanced returns the component that we passed in.
Essentially, connectAdvanced updates itself internally based on changes in store s to update the real components.
What is the update function for subscriptions?
This can be easily seen when subscribing internally to connectAdvanced:
subscription.onStateChange = checkForUpdates subscription.trySubscribe()
The function of the subscription is checkForUpdates, and what matters is what this checkForUpdates does to update the components.A reducer is built into useReducer in connectAdvanced, and what this function does is dispatch an action to trigger an update when the precondition (state change) is established.
How can I judge the state change?
This is a good question to understand, because every time redux returns a new state.Just decide if the references to the States before and after are the same.
connect Core--ConneAdvanced
ConneAdvanced is a relatively heavy higher-order function. The update mechanism is outlined above, but many of the specific practices are implemented in ConneAdvanced.The source is long, the logic is a bit complex, and I've written detailed comments.The process of viewing requires thinking about the call relationships between functions and their purposes, the meaning of each variable, and with the above conclusions, it is not difficult to understand.
// This is a library of static methods to preserve components import hoistStatics from 'hoist-non-react-statics' import React, { useContext, useMemo, useEffect, useLayoutEffect, useRef, useReducer } from 'react' import { isValidElementType, isContextConsumer } from 'react-is' import Subscription from '../utils/Subscription' import { ReactReduxContext } from './Context' const EMPTY_ARRAY = [] const NO_SUBSCRIPTION_ARRAY = [null, null] // Built-in reducer function storeStateUpdatesReducer(state, action) { const [, updateCount] = state return [action.payload, updateCount + 1] } const initStateUpdates = () => [null, 0] // React currently throws a warning when using useLayoutEffect on the server. // To get around it, we can conditionally useEffect on the server (no-op) and // useLayoutEffect in the browser. We need useLayoutEffect because we want // `connect` to perform sync updates to a ref to save the latest props after // a render is actually committed to the DOM. // My own translation of the above English comment: // react warns when useLayoutEffect is used in a server-side environment. To solve this problem, useEffect needs to be used on the server-side and useLayoutEffect on the browser-side. // useLayoutEffect synchronously invokes callbacks into all DOM changes. // So you need to use it in a browser environment, because connect will synchronize the update ref to save the latest props after rendering is committed to the DOM // The ReactHooks document describes useLayoutEffect: The update plan inside useLayoutEffect will be refreshed synchronously before the browser performs drawing. // useEffect's effectects will be executed after each rendering round, and useLayoutEffect's effects will be executed before drawing after the dom changes. // What effect ect does here is update // By the time the server renders, the page has come out, and it is possible that the js has not been loaded yet. // So you need to use useEffect in the SSR phase to make sure that after the page is taken over by js, you update it if you need to. // There is no such problem in the browser environment // Determine whether it's server or browser based on whether there's a window const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect export default function connectAdvanced( selectorFactory, // options object: { // Get the name of the component after it is wrapped by connect getDisplayName = name => `ConnectAdvanced(${name})`, // For display of error message methodName = 'connectAdvanced', // Direct translated English comment: If defined, an attribute named this value will be added to the props passed to the packaged component.Its value will be the number of times the component is rendered, which is useful for tracking unnecessary re-rendering.Default value: undefined renderCountProp = undefined, // Should the connect component respond to changes in store s shouldHandleStateChanges = true, // This is required when multiple stores are used, in order to distinguish which store to get storeKey = 'store', // If true, a reference is stored in the wrapped component instance. // And obtained by getWrappedInstance(). withRef = false, // Used to pass ref s in forwardRef = false, // context used internally by components that users can customize context = ReactReduxContext, // The remaining configuration items, selectorFactory should use ...connectOptions } = {} ) { //Some error logic was omitted // Get context const Context = context return function wrapWithConnect(WrappedComponent) { const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component' const displayName = getDisplayName(wrappedComponentName) // Define selectorFactoryOptions in preparation for constructing selectors const selectorFactoryOptions = { ...connectOptions, getDisplayName, methodName, renderCountProp, shouldHandleStateChanges, storeKey, displayName, wrappedComponentName, WrappedComponent } const { pure } = connectOptions /* Call createChildSelector => createChildSelector (store) (state, ownProps) createChildSelector Returns a parameterized call to selectorFactory, which is actually returned internally according to options.pure impureFinalPropsSelectorFactory Or a call to pureFinalPropsSelectorFactory, which requires parameters (state, ownProps) */ function createChildSelector(store) { // Here is the call to finalPropsSelectorFactory in selectorFactory.js, passing in dispatch, and options return selectorFactory(store.dispatch, selectorFactoryOptions) } // Pure means something similar to React's PureComponent here, depending on whether it is pure mode or not to determine whether updates need to be optimized const usePureOnlyMemo = pure ? useMemo : callback => callback() function ConnectFunction(props) { // Props changes, get the latest context,forwardedRef, and other props for components const [propsContext, forwardedRef, wrapperProps] = useMemo(() => { const { context, forwardedRef, ...wrapperProps } = props return [context, forwardedRef, wrapperProps] }, [props]) // The propsContext or Context changes, deciding which context to use and preferring if it exists const ContextToUse = useMemo(() => { // Users may replace ReactReduxContext with a custom context to cache which context instance we should use // Users may optionally pass in a custom context instance to use instead of our ReactReduxContext. // Memoize the check that determines which context instance we should use. return propsContext && propsContext.Consumer && isContextConsumer(<propsContext.Consumer />) ? propsContext : Context }, [propsContext, Context]) // Get store in context from upper components // Returns the current value of the context, that is, the store, when the context of the upper component has recently changed const contextValue = useContext(ContextToUse) // store must exist in prop or context // Determine if store is from a store in props const didStoreComeFromProps = Boolean(props.store) // Determine if the store is from a store in the context const didStoreComeFromContext = Boolean(contextValue) && Boolean(contextValue.store) // Remove the store from the context and prepare it for injection into the component after being processed by the selector.Prefer stores in props const store = props.store || contextValue.store // Create selector only when store changes // ChildPropsSelector call method: childPropsSelector(dispatch, options) const childPropsSelector = useMemo(() => { // The creation of selector depends on the incoming store // Recreate this selector whenever the store changes return createChildSelector(store) }, [store]) const [subscription, notifyNestedSubs] = useMemo(() => { if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY // If the store is from props, the subscription instance is no longer passed in, otherwise the subscription instance passed in from the context is used const subscription = new Subscription( store, didStoreComeFromProps ? null : contextValue.subscription ) const notifyNestedSubs = subscription.notifyNestedSubs.bind( subscription ) return [subscription, notifyNestedSubs] }, [store, didStoreComeFromProps, contextValue]) // contextValue is the store, overwrites the store once and injects the subscription so that the connect ed component can get the subscription in the context const overriddenContextValue = useMemo(() => { if (didStoreComeFromProps) { // If the component is directly subscribed to a store from props, use the context from props directly return contextValue } // Otherwise, put this component's subscription instance into context, so that // connected descendants won't update until after this component is done // Interpretation: // If the store is retrieved from the context, place the subscription in the context. // To ensure that the subcomponents that are connect ed before the component update is complete will not be updated return { ...contextValue, subscription } }, [didStoreComeFromProps, contextValue, subscription]) // Built-in reducer to update components, which is used in the checkForUpdates function as the core of the update mechanism const [ [previousStateUpdateResult], forceComponentUpdateDispatch ] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates) if (previousStateUpdateResult && previousStateUpdateResult.error) { throw previousStateUpdateResult.error } // Set up refs to coordinate values between the subscription effect and the render logic /* * Official explanation: * useRef Returns a mutable ref object whose.current property is initialized as an incoming parameter (initialValue). * The returned ref object remains unchanged throughout the life cycle of the component. * * ref Not only for DOM, the current property of useRef() can be used to hold values, similar to the instance property of a class * * */ const lastChildProps = useRef() // props of components, including, store, dispatch from parent const lastWrapperProps = useRef(wrapperProps) // The component itself comes from the props of the parent component const childPropsFromStoreUpdate = useRef() // Mark if props from store have been updated const renderIsScheduled = useRef(false) // Mark when to update /* * actualChildProps Is the props that are really going to be injected into the component * */ const actualChildProps = usePureOnlyMemo(() => { // Tricky logic here: // - This render may have been triggered by a Redux store update that produced new child props // - However, we may have gotten new wrapper props after that // If we have new child props, and the same wrapper props, we know we should use the new child props as-is. // But, if we have new wrapper props, those might change the child props, so we have to recalculate things. // So, we'll use the child props from store update only if the wrapper props are the same as last time. /* * Interpretation: * This rendering will be triggered when an update to the store generates a new props, however, we may receive a new props from the parent component after that, if there is a new props, * And props from parent components are unchanged, so we should update them based on the new child props.However, updates to props from parent components can also result in changes to the overall props and have to be recalculated. * So only update if the new props change and the props from the parent component agree with the last one (the judgement in the code below is true) * * That is, relay only on props updates caused by store changes * */ if ( childPropsFromStoreUpdate.current && wrapperProps === lastWrapperProps.current ) { return childPropsFromStoreUpdate.current } return childPropsSelector(store.getState(), wrapperProps) }, [store, previousStateUpdateResult, wrapperProps]) // We need this to execute synchronously every time we re-render. However, React warns // about useLayoutEffect in SSR, so we try to detect environment and fall back to // just useEffect instead to avoid the warning, since neither will run anyway. /* * We need to synchronize this effect every time we re-render.But react will drop a warning for useLayoutEffect in the case of SSR. * So the end result of useIsomorphic LayoutEffect is a useEffect or useLayoutEffect judged by the environment.Use useEffect when rendering on the server side. * Since useEffect will wait until js takes over the page in this case, it will not warning * */ /* * Overall, there are two useIsomorphic LayoutEffects above and below, but the difference is when they are executed. * * The first one does not pass in an array of dependencies, so effect s execute each time they are re-rendered, responsible for each re-rendered * When checking for changes in data from store s, listeners are notified to update * * The second depends on store, subscription, child PropsSelector.So during these three changes, execute the effect. * Its internal effects do something different from the first one, defining the update function checkForUpdates, subscribing to the update function, so that when the first effect responds to store updates, * Update functions can be executed as listener s to update the page * * */ useIsomorphicLayoutEffect(() => { lastWrapperProps.current = wrapperProps // Get the props of the component itself lastChildProps.current = actualChildProps // Get props injected into the component renderIsScheduled.current = false // Indicates that the rendering phase has passed // If the render was from a store update, clear out that reference and cascade the subscriber update // If the props from the store are updated, notify listeners to execute, that is, execute the previously subscribed this.handleChangeWrapper (in the Subscription class). // handleChangeWrapper calls onStateChange, which is the function checkForUpdates assigned below that updates the page if (childPropsFromStoreUpdate.current) { childPropsFromStoreUpdate.current = null notifyNestedSubs() } }) // Our re-subscribe logic only runs when the store/subscription setup changes // Resubscription is only executed when subscription changes within the store.These two changes mean re-subscribing, since guaranteeing the latest data is delivered, previous subscriptions are no longer meaningful useIsomorphicLayoutEffect(() => { // If there is no subscription, return directly, shouldHandleStateChanges defaults to true, so the default will continue if (!shouldHandleStateChanges) return // Capture values for checking if and when this component unmounts // When a component is uninstalled, use a closure to declare whether two variable markers have been unsubscribed and the error object let didUnsubscribe = false let lastThrownError = null // When the store or subscription changes, the callback is re-executed, enabling re-subscription const checkForUpdates = () => { if (didUnsubscribe) { // If you cancel your subscription, do nothing return } // Get the latest state const latestStoreState = store.getState() let newChildProps, error try { // Use selector to get the latest props newChildProps = childPropsSelector( latestStoreState, lastWrapperProps.current ) } catch (e) { error = e lastThrownError = e } if (!error) { lastThrownError = null } // If props do not change, just notify listeners of updates if (newChildProps === lastChildProps.current) { /* * In a browser environment, useLayoutEffect is executed after the DOM changes and before drawing. * Since the useIsomorphicLayoutEffect above executes at this time setting renderIsScheduled.current to false, * So go inside and make sure you trigger the update at the right time * * */ if (!renderIsScheduled.current) { notifyNestedSubs() } } else { /* * If props change, cache the new props and set childPropsFromStoreUpdate.current to the new props to facilitate the first * useIsomorphicLayoutEffect When executed, it is recognized that props are indeed updated * */ lastChildProps.current = newChildProps childPropsFromStoreUpdate.current = newChildProps renderIsScheduled.current = true // When dispatch has built-in action s, the ConnectFunction component is updated to update the component forceComponentUpdateDispatch({ type: 'STORE_UPDATED', payload: { latestStoreState, error } }) } } // The role of onStateChange is also listener.In the provider, assign a value to update listeners.Assign checkForUpdates to ConnectFunction // What checkForUpdates does is update the ConnectFunction itself, which corresponds to a listener, depending on the props subscription.onStateChange = checkForUpdates subscription.trySubscribe() // Run once after the first rendering to synchronize data from the store checkForUpdates() // Returns a function to unsubscribe from when a component is uninstalled const unsubscribeWrapper = () => { didUnsubscribe = true subscription.tryUnsubscribe() if (lastThrownError) { throw lastThrownError } } return unsubscribeWrapper }, [store, subscription, childPropsSelector]) // Inject the props of the component into the real component we passed in const renderedWrappedComponent = useMemo( () => <WrappedComponent {...actualChildProps} ref={forwardedRef} />, [forwardedRef, WrappedComponent, actualChildProps] ) const renderedChild = useMemo(() => { if (shouldHandleStateChanges) { // If this component is subscribed to store updates, we need to pass its own // subscription instance down to our descendants. That means rendering the same // Context instance, and putting a different value into the context. /* * Interpretation: If this component subscribes to updates from store s, it needs to download instances it subscribes to, meaning it and it All descendant components render the same Context instance, but may put different values into the context Layer one more Provider and place the overridden context in value. What does this mean?That is, there is a connected component and a nested connected component. Ensure that the two subscription s obtained from the context are the same, and that they may both add new values to the context. I added one, and so did my subcomponents.The final context is the integration of the value s of all components, and subscription s are always the same * */ return ( <ContextToUse.Provider value={overriddenContextValue}> {renderedWrappedComponent} </ContextToUse.Provider> ) } // Depending on the received context, the incoming component, and the change in the value of the context, it determines whether or not to re-render return renderedWrappedComponent }, [ContextToUse, renderedWrappedComponent, overriddenContextValue]) return renderedChild } // Rendering logic based on pure const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction // Add Component Name Connect.WrappedComponent = WrappedComponent Connect.displayName = displayName // If forwardRef is true, ref is injected into the Connect component for easy access to DOM instances of the component if (forwardRef) { const forwarded = React.forwardRef(function forwardConnectRef( props, ref ) { return <Connect {...props} forwardedRef={ref} /> }) forwarded.displayName = displayName forwarded.WrappedComponent = WrappedComponent return hoistStatics(forwarded, WrappedComponent) } // Static methods to preserve components return hoistStatics(Connect, WrappedComponent) } }
Having looked at the source code, let's summarize the update mechanism for the connect ed component in React-Redux as a whole:
Three of these elements are essential:
- Depending on who changes (store)
- Update function (checkForUpdates)
- Subscription linking store s to update functions
Within the connectAdvanced function, get the store from the context, get the subscription instance (possibly from the context or a new creation), and create the update function checkForUpdates.
Subscribe or resubscribe when a component is initialized, or when a store, Subscription instance, or selector changes.Every time a component is updated, check to see if the store has changed and notify the update if it has changed.
In fact, checkForUpdates is executed, essentially calling the built-in reducer update component.Each update causes the selector to recalculate, so the component always gets the latest props.So, the bottom level of the update mechanism
This is achieved through ConneAdvanced's built-in Reeducer.
summary
So far, the source code for React-Redux has been interpreted around the common functions.Back to the first three questions in the article:
- How Provider puts store s into context s
- How to inject a state and dispatch (or a function that calls dispatch) from the store into the props of a component
- We all know that in Redux, store changes and UI updates can be achieved by subscribing to a function of an update page through store.subscribe(), and how React-Redux does that
store changes, and connect ed components update
Now it should be clear that these three questions correspond to the three core concepts of React-Redux:
- Provider injects data from top level
- Selector generates props for components
- Update mechanism for React-Redux
They work together, which is how React-Redux works: Provider puts data into context, connect s removes store from context, gets mapStateToProps, mapDispatchToProps, and generates Selector as props injection component using selectorFactory.Subscribe to store changes and get the latest props for each update component.
The best way to read the source code is to identify the problem and read it purposefully.At the beginning, I was hard-headed. The more I looked, the more I lost sight of it. I learned a lot after I changed the way I thought you were.
Welcome to my public number: one front end at a time, sharing what I understand on an irregular basis