庖丁解牛React-Redux(一): connectAdvanced
转眼间2017年已经过了一半了,看到之前有人问是否完成了自己半年的计划,答案是:当然没有啦。感觉自己现在对技术产生了敬畏,因为要学习的知识是在是太多了,而自己的时间和精力却很难达到目标,目前处在比较焦虑的状态。自己是年初进入掘金的,半年内虽然文章的阅读量不错但是关注度太低,半年就混了40个关注,说来真是惭愧。 . ├── PropTypes.js ├── Subscription.js ├── shallowEqual.js ├── verifyPlainObject.js ├── warning.js └── wrapActionCreators.js 首先来看一下index.js: import Provider,{ createProvider } from './components/Provider' import connectAdvanced from './components/connectAdvanced' import connect from './connect/connect' export { Provider,createProvider,connectAdvanced,connect } 我们可以看出来,React-Redux对外提供的API有四个: connectAdvanced 其实我在看React-Redux源码之前都不知道有这个API,为了方便后面的源码理解,我们介绍一下 参数:
2. [methodName] (String) 用来在错误信息中显示,默认值为 返回: 函数返回一个高阶组件,该高阶组件将从store的state中构建的props传递给被包裹组件。 例如: // 按照用户信息选择性传入todos的部分信息 import * as actionCreators from './actionCreators' import { bindActionCreators } from 'redux' function selectorFactory(dispatch) { let ownProps = {} let result = {} const actions = bindActionCreators(actionCreators,dispatch) const addTodo = (text) => actions.addTodo(ownProps.userId,text) return (nextState,nextOwnProps) => { const todos = nextState.todos[nextProps.userId] const nextResult = { ...nextOwnProps,todos,addTodo } ownProps = nextOwnProps if (!shallowEqual(result,nextResult)) result = nextResult return result } } export default connectAdvanced(selectorFactory)(TodoApp) 讲了这么多,我们看看 //代码整体结构 function connectAdvanced( selectorFactory,{ getDisplayName = name => `ConnectAdvanced(${name})`,methodName = 'connectAdvanced',renderCountProp = undefined,shouldHandleStateChanges = true,storeKey = 'store',withRef = false,...connectOptions } = {} ) { return function wrapWithConnect(WrappedComponent) { class Connect extends Component { //...... return hoistStatics(Connect,WrappedComponent) } } 函数接受两个参数: import hoistStatics from 'hoist-non-react-statics' 作用是将 其实对于React-Redux之所以可以使得 let hotReloadingVersion = 0 const dummyState = {} function noop() {} function connectAdvanced( selectorFactory,...connectOptions } = {} ) { const subscriptionKey = storeKey + 'Subscription' const version = hotReloadingVersion++ const contextTypes = { [storeKey]: storeShape,[subscriptionKey]: subscriptionShape,} const childContextTypes = { [subscriptionKey]: subscriptionShape,} return function wrapWithConnect(WrappedComponent) { const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component' const displayName = getDisplayName(wrappedComponentName) const selectorFactoryOptions = { ...connectOptions,getDisplayName,methodName,renderCountProp,shouldHandleStateChanges,storeKey,withRef,displayName,wrappedComponentName,WrappedComponent } class Connect extends Component { } return hoistStatics(Connect,WrappedComponent) } } 上面的代码并没有什么难以理解的, class Connect extends Component { constructor(props,context) { super(props,context) this.version = version this.state = {} this.renderCount = 0 this.store = props[storeKey] || context[storeKey] this.propsMode = Boolean(props[storeKey]) this.setWrappedInstance = this.setWrappedInstance.bind(this) this.initSelector() this.initSubscription() } getChildContext() { const subscription = this.propsMode ? null : this.subscription return { [subscriptionKey]: subscription || this.context[subscriptionKey] } } componentDidMount() { if (!shouldHandleStateChanges) return this.subscription.trySubscribe() this.selector.run(this.props) if (this.selector.shouldComponentUpdate) this.forceUpdate() } componentWillReceiveProps(nextProps) { this.selector.run(nextProps) } shouldComponentUpdate() { return this.selector.shouldComponentUpdate } componentWillUnmount() { if (this.subscription) this.subscription.tryUnsubscribe() this.subscription = null this.notifyNestedSubs = noop this.store = null this.selector.run = noop this.selector.shouldComponentUpdate = false } getWrappedInstance() { return this.wrappedInstance } setWrappedInstance(ref) { this.wrappedInstance = ref } initSelector() { const sourceSelector = selectorFactory(this.store.dispatch,selectorFactoryOptions) this.selector = makeSelectorStateful(sourceSelector,this.store) this.selector.run(this.props) } initSubscription() { if (!shouldHandleStateChanges) return 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() { this.selector.run(this.props) if (!this.selector.shouldComponentUpdate) { this.notifyNestedSubs() } else { this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate this.setState(dummyState) } } notifyNestedSubsOnComponentDidUpdate() { this.componentDidUpdate = undefined this.notifyNestedSubs() } isSubscribed() { return Boolean(this.subscription) && this.subscription.isSubscribed() } 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)) } } } Connect.WrappedComponent = WrappedComponent Connect.displayName = displayName Connect.childContextTypes = childContextTypes Connect.contextTypes = contextTypes Connect.propTypes = contextTypes if (process.env.NODE_ENV !== 'production') { Connect.prototype.componentWillUpdate = function componentWillUpdate() { // We are hot reloading! if (this.version !== version) { this.version = version this.initSelector() if (this.subscription) this.subscription.tryUnsubscribe() this.initSubscription() if (shouldHandleStateChanges) this.subscription.trySubscribe() } } } 我们首先来看构造函数: constructor(props,context) { super(props,context) this.version = version this.state = {} this.renderCount = 0 this.store = props[storeKey] || context[storeKey] this.propsMode = Boolean(props[storeKey]) this.setWrappedInstance = this.setWrappedInstance.bind(this) this.initSelector() this.initSubscription() } 首先我们先看看用来初始化 //Connect类方法 initSelector() { const sourceSelector = selectorFactory(this.store.dispatch,selectorFactoryOptions) this.selector = makeSelectorStateful(sourceSelector,this.store) this.selector.run(this.props) } //connectAdvanced外定义的函数 function makeSelectorStateful(sourceSelector,store) { // wrap the selector in an object that tracks its results between runs. 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 } } } return selector } 我们知道, 再看 // 为连接到redux的store的组件以及嵌套的后代组件封装订阅逻辑,以确保祖先组件在后代组件之前刷新 const CLEARED = null const nullListeners = { notify() {} } function createListenerCollection() { //代码逻辑来源与store中 let current = [] let next = [] return { clear() { next = CLEARED current = CLEARED },notify() { const listeners = current = next for (let i = 0; i < listeners.length; i++) { listeners[i]() } },subscribe(listener) { let isSubscribed = true if (next === current) next = current.slice() next.push(listener) return function unsubscribe() { if (!isSubscribed || current === CLEARED) return isSubscribed = false if (next === current) next = current.slice() next.splice(next.indexOf(listener),1) } } } } export default class Subscription { constructor(store,onStateChange) { this.store = store this.parentSub = parentSub this.onStateChange = onStateChange this.unsubscribe = null this.listeners = nullListeners } addNestedSub(listener) { this.trySubscribe() return this.listeners.subscribe(listener) } notifyNestedSubs() { this.listeners.notify() } isSubscribed() { return Boolean(this.unsubscribe) } trySubscribe() { if (!this.unsubscribe) { this.unsubscribe = this.parentSub ? this.parentSub.addNestedSub(this.onStateChange) : this.store.subscribe(this.onStateChange) this.listeners = createListenerCollection() } } tryUnsubscribe() { if (this.unsubscribe) { this.unsubscribe() this.unsubscribe = null this.listeners.clear() this.listeners = nullListeners } } } 首先我们先看函数 { clear,notify,subscribe } 作为对外接口,分别用来清除当前存储的listener、通知、订阅,其目的就是实现一个监听者模式。然后类 //this.propsMode来自于constructor中的this.propsMode = Boolean(props[storeKey]),storeKey默认为`store` const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey] 我们知道 情况1: 如果 this.store.subscribe(this.onStateChange)。 情况2: 如果当前组件并不是通往根节点的路径中第一个连接到Redux的store的组件,也就是父组件中存在已经连接到Redux的store的组件。这时候,必须要保证下层的组件响应 getChildContext() { const subscription = this.propsMode ? null : this.subscription return { [subscriptionKey]: subscription || this.context[subscriptionKey] } } 因此在子组件(红色)中就可以通过 this.parentSub.addNestedSub(this.onStateChange) 这样我们将子组件处理store中state的函数添加到 情况3: 如上图所示,右边的组件是通过属性prop的方式传入了 this.store.subscribe(this.onStateChange)。 情况4: 如上图所示,右下方的组件的父组件(紫色)是通过props传入 getChildContext() { const subscription = this.propsMode ? null : this.subscription return { [subscriptionKey]: subscription || this.context[subscriptionKey] } } 父组件对子组件暴露
在上面这个例子中,如果发出 initSubscription() { if (!shouldHandleStateChanges) return const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey] this.subscription = new Subscription(this.store,this.onStateChange.bind(this)) this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription) } 如果当前的store不是以props的方式传入的,那么 componentDidMount() { if (!shouldHandleStateChanges) return this.subscription.trySubscribe() this.selector.run(this.props) if (this.selector.shouldComponentUpdate) this.forceUpdate() } componentWillReceiveProps(nextProps) { this.selector.run(nextProps) } shouldComponentUpdate() { return this.selector.shouldComponentUpdate } componentWillUnmount() { if (this.subscription) this.subscription.tryUnsubscribe() this.subscription = null this.notifyNestedSubs = noop this.store = null this.selector.run = noop this.selector.shouldComponentUpdate = false } 组件在 接着我们介绍其他的类方法: getWrappedInstance() { return this.wrappedInstance } setWrappedInstance(ref) { this.wrappedInstance = ref } onStateChange() { this.selector.run(this.props) if (!this.selector.shouldComponentUpdate) { this.notifyNestedSubs() } else { this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate this.setState(dummyState)//dummyState === {} } } notifyNestedSubsOnComponentDidUpdate() { this.componentDidUpdate = undefined this.notifyNestedSubs() } 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)) } } createElement(WrappedComponent,this.addExtraProps(selector.props)) 如果你对上面语句不太熟悉,其实上面代码等同于: return ( <WrappedComponent {...this.addExtraProps(selector.props)} /> ) 其实所谓的 if (process.env.NODE_ENV !== 'production') { Connect.prototype.componentWillUpdate = function componentWillUpdate() { // We are hot reloading! if (this.version !== version) { this.version = version this.initSelector() if (this.subscription) this.subscription.tryUnsubscribe() this.initSubscription() if (shouldHandleStateChanges) this.subscription.trySubscribe() } } } React-Redux在生产环境下是不支持热重载的,只有在开发环境下提供这个功能。在开发环境中,组件在 Providerimport { Component,Children } from 'react' import PropTypes from 'prop-types' import { storeShape,subscriptionShape } from '../utils/PropTypes' import warning from '../utils/warning' let didWarnAboutReceivingStore = false function warnAboutReceivingStore() { if (didWarnAboutReceivingStore) { return } didWarnAboutReceivingStore = true warning( '<Provider> does not support changing `store` on the fly. ' + 'It is most likely that you see this error because you updated to ' + 'Redux 2.x and React Redux 2.x which no longer hot reload reducers ' + 'automatically. See https://github.com/reactjs/react-redux/releases/' + 'tag/v2.0.0 for the migration instructions.' ) } export function createProvider(storeKey = 'store',subKey) { const subscriptionKey = subKey || `${storeKey}Subscription` class Provider extends Component { getChildContext() { return { [storeKey]: this[storeKey],[subscriptionKey]: null } } constructor(props,context) { super(props,context) this[storeKey] = props.store; } render() { return Children.only(this.props.children) } } 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,} Provider.displayName = 'Provider' return Provider } export default createProvider() 首先我们看看函数 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |