React-redux原理探索
先看一段react-redux代码再探索原理
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') );
先调用redux的createStore生成store,再将store作为属性传给Provider组件。
现在看看react-redux源代码。
index.js页暴露了以下几个接口
export { Provider, createProvider, connectAdvanced, connect }
上面的demo使用的是Provider组件,Provider的主要作用是使得其所有子组件可以通过context访问到Redux的store,现在深入了解一下Provider组件的实现原理。
class Provider extends Component { // 将store放入context使子孙组件能够访问到store getChildContext() { return { [storeKey]: this[storeKey], [subscriptionKey]: null } } constructor(props, context) { super(props, context) // 获取redux实例 this[storeKey] = props.store; } render() { // 使整个应用成为Provider的子组件 // 确保Provider组件的直接子级为单个封闭元素,切勿多个组件平行放置。 return Children.only(this.props.children) } } // Redux 2.x 与React-Redux 2.x不再支持热重载的reducer,所以在非生产环境下, // 我们会为Provider添加生命周期函数componentWillReceiveProps,如果store的值发生了变化,就会在提供警告提示 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 }
connect的作用就是将state和dispatch绑定到react组件中,使得组件可以访问到redux,下面为connect使用demo
export default connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {})(ReactComponent)
connect源代码比较长,把connect的核心实现简化提取出来是下面的形式,最终返回的是把state和dispatch绑定到Connect的组件。
funtion connect(mapStateToProps,mapDispatchToProps,mergeProps,{一堆props}) {
return function wrapWithConnect(WrappedComponent) { class Connect extends Component { } return hoistStatics(Connect, WrappedComponent) } }
其中hoistStatics(Connect, WrappedComponent)是自动把所有绑定在WrappedComponent对象上的非React方法都绑定到Connect上。
先看看传入的四个参数mapStateToProps,mapDispatchToProps,mergeProps,{一堆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 })
通过match方法生成新的函数传递给connectHOC(生成Connect组件的方法)
const mapStateToProps = state => ({
todos: []
})
- 不传参时返回函数(函数运行参数传入dispatch,返回对象{dispatch:function dispatch(){xxx}});
- 传参为函数时返回函数(函数运行传参dispatch返回对象,例如下边函数会返回{onClick:function(){xxx}})
const mapDispatchToProps = (dispatch, ownProps) => ({ onClick: () => dispatch(setVisibilityFilter(ownProps.filter)) })
- 传参为对象时返回函数(函数运行传参dispatch,返回{xxx:function(){xxx}})
initMergeProps不传参默认返回 { ...ownProps, ...stateProps, ...dispatchProps }将所有props全部合成一个对象
这三种参数全部初始化完毕后执行connectHOC生成Connect组件,Connect组件会初始化两样东西
- initSelector:selector的主要作用是通过执行initMapStateToProps,initMapDispatchToProps,initMergeProps生成的函数计算新的props,并返回纯对象(plain object),将这个对象作为props传递给被包裹的组件(WrappedComponent)
initSelector() { // 首先调用selectorFactory从而初始化sourceSelector,我们并不会直接调用sourceSelector,而是为了程序的健壮, // 通过将sourceSelector作为参数调用makeSelectorStateful,返回更加安全的selector。 // 从此之后,我们想要生成新的props只需要调用selector.run函数。 // 在selector.run函数中对sourceSelector的异常做了处理,并用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) }
通过执行selector.run设置selector.shouldComponentUpdate再通过shouldComponentUpdate判断selector.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为store增加监听事件,在store数据变化时执行onStateChange函数
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) }
onStateChange
函数是store发生改变的回调函数,当回调onStateChange
方法时,会通过selector计算新的props,如果计算selcetor的结果中shouldComponentUpdate
为false
,表示不需要刷新当前组件仅需要通知子组件更新。如果shouldComponentUpdate
为true
,会通过设置this.setState({})
来刷新组件,并使得在组件更新结束之后,通知子组件更新。
onStateChange() { this.selector.run(this.props) if (!this.selector.shouldComponentUpdate) { this.notifyNestedSubs() } else { this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate this.setState(dummyState) } }
在render操作时将selector生成的新props作为参数传到WrappedComponent组件中,WrappedComponent组件就是connect()(ReactComponent)中传入的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)) } }
将selector.props中的属性传到WrappedComponent,这个props是经过计算的所有props的合集。
参考:
https://segmentfault.com/a/1190000010188279