Take a look at a section of react-redux code before exploring principles
import React from 'react'; import { render } from 'react-dom'; import { createStore } from 'redux'; import { Provider } from 'react-redux'; import routes from './router'; import reducer from './reducer'; const store = createStore( reducer ); render( <Provider store={store}> {routes} </Provider>, document.getElementById('root') );
Call redux's createStore to generate the store, and pass the store as a property to the Provider component.
Now look at the react-redux source code.
The index.js page exposes the following interfaces
export { Provider, createProvider, connectAdvanced, connect }
The demo above uses the Provider component, whose main purpose is to make all its subcomponents accessible to the Redux store through the context. Now you can get a better understanding of how the Provider component works.
class Provider extends Component { // Placing stores in context s enables descendant components to access stores getChildContext() { return { [storeKey]: this[storeKey], [subscriptionKey]: null } } constructor(props, context) { super(props, context) // Get redux instance this[storeKey] = props.store; } render() { // Make the entire application a subcomponent of Provider // Ensure that the direct child of the Provider component is a single enclosed element and that multiple components are not placed in parallel. return Children.only(this.props.children) } } // Redux 2.x and React-Redux 2.x no longer support hot-overloaded reducer s, so in non-production environments, // We will add the life cycle function componentWillReceiveProps to the Provider, which will alert you if the store value changes if (process.env.NODE_ENV !== 'production') { Provider.prototype.componentWillReceiveProps = function (nextProps) { if (this[storeKey] !== nextProps.store) { warnAboutReceivingStore() } } } Provider.propTypes = { store: storeShape.isRequired, children: PropTypes.element.isRequired, } Provider.childContextTypes = { [storeKey]: storeShape.isRequired, [subscriptionKey]: subscriptionShape, } return Provider }
The purpose of connect is to bind state and dispatch to the react component so that the component can access redux. Here's demo for connect
export default connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {})(ReactComponent)
Connect source code is long, simplifying the core implementation of connection is extracted in the following form, and the final return is the component that binds state and dispatch to Connect.
funtion connect(mapStateToProps,mapDispatchToProps,mergeProps,{A pile props}) { return function wrapWithConnect(WrappedComponent) { class Connect extends Component { } return hoistStatics(Connect, WrappedComponent) } }
hoistStatics(Connect, WrappedComponent) automatically binds all non-React methods bound to WrappedComponent objects to Connect.
First look at the four parameters passed in: mapStateToProps,mapDispatchToProps,mergeProps, {a bunch of props}
const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps') const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps') const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps') return connectHOC(selectorFactory, { // used in error messages methodName: 'connect', // used to compute Connect's displayName from the wrapped component's displayName. getDisplayName: name => `Connect(${name})`, // if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes shouldHandleStateChanges: Boolean(mapStateToProps), // passed through to selectorFactory initMapStateToProps, initMapDispatchToProps, initMergeProps, pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, // any extra options args can override defaults of connect or connectAdvanced ...extraOptions })
Generate a new function through the match method and pass it to connectHOC (the method that generates the Connect component)
initMapStateToProps has two scenarios. It returns a function when it is not passed or null (running the pass-through state returns an empty object) and a function when it is a function (returning attributes passed in the state after execution, such as the function below returns {todo:[]}).
const mapStateToProps = state => ({ todos: [] })
There are three types of initMapDispatchToProps:
- Returns a function when no arguments are passed (the function run parameter is passed into dispatch and the object {dispatch:function dispatch(){xxx}} is returned);
- Returns a function when the parameter is a function (the function runs the parameter dispatch to return an object, for example, the lower function returns {onClick:function(){xx}}
const mapDispatchToProps = (dispatch, ownProps) => ({ onClick: () => dispatch(setVisibilityFilter(ownProps.filter)) })
- Returns a function when the parameter is an object (the function runs the parameter dispatch and returns {xxx:function(){xxx}})
initMergeProps returns {... OwnProps,... StateProps,... DispatchProps} by default to synthesize all props into one object
When all three parameters are initialized, perform connectHOC to generate the Connect component, which initializes two things
- The main function of initSelector:selector is to calculate a new props by executing the functions generated by initMapStateToProps, initMapDispatchToProps, initMergeProps, and return a plain object that is passed as props to the wrapped component (WrappedComponent)
initSelector() { // Initialize sourceSelector by calling selectorFactory first. We do not call sourceSelector directly, but for the robustness of the program. // A safer selector is returned by calling makeSelectorStateful with sourceSelector as an argument. // From then on, all we want to generate a new props is to call the selector.run function. // Exceptions to sourceSelector are handled in the selector.run function, and exceptions are recorded with sourceSelector.error // function pureFinalPropsSelector(nextState, nextOwnProps) { // return hasRunAtLeastOnce // ? handleSubsequentCalls(nextState, nextOwnProps) // : handleFirstCall(nextState, nextOwnProps) // } const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions) this.selector = makeSelectorStateful(sourceSelector, this.store) this.selector.run(this.props) }
Set selector.shouldComponentUpdate by executing selector.run and then determine if the selector.shouldComponentUpdate notification component is refreshed by shouldComponentUpdate
const selector = { run: function runComponentSelector(props) { try { const nextProps = sourceSelector(store.getState(), props) if (nextProps !== selector.props || selector.error) { selector.shouldComponentUpdate = true selector.props = nextProps selector.error = null } } catch (error) { selector.shouldComponentUpdate = true selector.error = error } } } shouldComponentUpdate() { return this.selector.shouldComponentUpdate }
- initSubscription adds listen events to the store and executes the onStateChange function when the store data changes
initSubscription() { if (!shouldHandleStateChanges) return // parentSub's source should match where store came from: props vs. context. A component // connected to the store via props shouldn't use subscription from context, or vice versa. const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey] this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this)) this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription) }
The onStateChange function is a callback function that store s change. When the onStateChange method is called back, a new props is calculated through selector. If shouldComponentUpdate is false in the result of selcetor calculation, it means that the current component does not need to be refreshed and only subcomponents need to be notified of updates.If shouldComponentUpdate is true, the component is refreshed by setting this.setState({}) and notifies the subcomponent of the update after it has finished.
onStateChange() { this.selector.run(this.props) if (!this.selector.shouldComponentUpdate) { this.notifyNestedSubs() } else { this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate this.setState(dummyState) } }
The new props generated by selector are passed as parameters to the WrappedComponent component during render operations, which is the ReactComponent passed in from connect()(ReactComponent)
addExtraProps(props) { if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props const withExtras = { ...props } if (withRef) withExtras.ref = this.setWrappedInstance if (renderCountProp) withExtras[renderCountProp] = this.renderCount++ if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription return withExtras } render() { const selector = this.selector selector.shouldComponentUpdate = false if (selector.error) { throw selector.error } else { return createElement(WrappedComponent, this.addExtraProps(selector.props)) } }
Pass the properties from selector.props to the WrappedComponent, which is a collection of all calculated props.
Original from cnblogs
Link: https://www.cnblogs.com/xcsun/p/9146342.html