1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
| import hoistStatics from 'hoist-non-react-statics' import invariant from 'invariant' import React, { Component, PureComponent } from 'react' import { isValidElementType, isContextConsumer } from 'react-is'
import { ReactReduxContext } from './Context'
const stringifyComponent = Comp => { try { return JSON.stringify(Comp) } catch (err) { return String(Comp) } }
export default function connectAdvanced( /* selectorFactory is a func that is responsible for returning the selector function used to compute new props from state, props, and dispatch. For example:
export default connectAdvanced((dispatch, options) => (state, props) => ({ thing: state.things[props.thingId], saveThing: fields => dispatch(actionCreators.saveThing(props.thingId, fields)), }))(YourComponent)
Access to dispatch is provided to the factory so selectorFactories can bind actionCreators outside of their selector as an optimization. Options passed to connectAdvanced are passed to the selectorFactory, along with displayName and WrappedComponent, as the second argument.
Note that selectorFactory is responsible for all caching/memoization of inbound and outbound props. Do not use connectAdvanced directly without memoizing results between calls to your selector, otherwise the Connect component will re-render on every state or props change. */ selectorFactory, // options object: { // the func used to compute this HOC's displayName from the wrapped component's displayName. // probably overridden by wrapper functions such as connect() getDisplayName = name => `ConnectAdvanced(${name})`,
// shown in error messages // probably overridden by wrapper functions such as connect() methodName = 'connectAdvanced',
// REMOVED: if defined, the name of the property passed to the wrapped element indicating the number of // calls to render. useful for watching in react devtools for unnecessary re-renders. renderCountProp = undefined,
// determines whether this HOC subscribes to store changes shouldHandleStateChanges = true,
// REMOVED: the key of props/context to get the store storeKey = 'store',
// REMOVED: expose the wrapped component via refs withRef = false,
// use React's forwardRef to expose a ref of the wrapped component forwardRef = false,
// the context consumer to use context = ReactReduxContext,
// additional options are passed through to the selectorFactory ...connectOptions } = {} ) { invariant( renderCountProp === undefined, `renderCountProp is removed. render counting is built into the latest React dev tools profiling extension` )
invariant( !withRef, 'withRef is removed. To access the wrapped instance, use a ref on the connected component' )
const customStoreWarningMessage = 'To use a custom Redux store for specific components, create a custom React context with ' + "React.createContext(), and pass the context object to React Redux's Provider and specific components" + ' like: <Provider context={MyContext}><ConnectedComponent context={MyContext} /></Provider>. ' + 'You may also pass a {context : MyContext} option to connect'
invariant( storeKey === 'store', 'storeKey has been removed and does not do anything. ' + customStoreWarningMessage ) // 存储context const Context = context
return function wrapWithConnect(WrappedComponent) { if (process.env.NODE_ENV !== 'production') { invariant( isValidElementType(WrappedComponent), `You must pass a component to the function returned by ` + `${methodName}. Instead received ${stringifyComponent( WrappedComponent )}` ) }
const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component'
const displayName = getDisplayName(wrappedComponentName)
const selectorFactoryOptions = { ...connectOptions, getDisplayName, methodName, renderCountProp, shouldHandleStateChanges, storeKey, displayName, wrappedComponentName, WrappedComponent }
const { pure } = connectOptions // pure参数决定是继承Component还是PureComponent let OuterBaseComponent = Component
if (pure) { OuterBaseComponent = PureComponent }
function makeDerivedPropsSelector() { let lastProps let lastState let lastDerivedProps let lastStore let lastSelectorFactoryOptions let sourceSelector
return function selectDerivedProps( state, props, store, selectorFactoryOptions ) { if (pure && lastProps === props && lastState === state) { // 直接返回上一次生成的props,避免不必要的渲染工作 return lastDerivedProps }
if ( store !== lastStore || lastSelectorFactoryOptions !== selectorFactoryOptions ) { // 更新数据 lastStore = store lastSelectorFactoryOptions = selectorFactoryOptions sourceSelector = selectorFactory( store.dispatch, selectorFactoryOptions ) }
lastProps = props lastState = state // 生成新的需要注入的props,这里传入的props是ownProps const nextProps = sourceSelector(state, props)
lastDerivedProps = nextProps return lastDerivedProps } }
function makeChildElementSelector() { let lastChildProps, lastForwardRef, lastChildElement, lastComponent
return function selectChildElement( WrappedComponent, childProps, forwardRef ) { if ( childProps !== lastChildProps || forwardRef !== lastForwardRef || lastComponent !== WrappedComponent ) { lastChildProps = childProps lastForwardRef = forwardRef lastComponent = WrappedComponent lastChildElement = ( <WrappedComponent {...childProps} ref={forwardRef} /> ) }
return lastChildElement } }
class Connect extends OuterBaseComponent { constructor(props) { super(props) invariant( forwardRef ? !props.wrapperProps[storeKey] : !props[storeKey], 'Passing redux store in props has been removed and does not do anything. ' + customStoreWarningMessage ) this.selectDerivedProps = makeDerivedPropsSelector() this.selectChildElement = makeChildElementSelector() this.indirectRenderWrappedComponent = this.indirectRenderWrappedComponent.bind( this ) }
indirectRenderWrappedComponent(value) { // calling renderWrappedComponent on prototype from indirectRenderWrappedComponent bound to `this` return this.renderWrappedComponent(value) }
renderWrappedComponent(value) { // 这里的value就是最开始在Provider里面定义的state invariant( value, `Could not find "store" in the context of ` + `"${displayName}". Either wrap the root component in a <Provider>, ` + `or pass a custom React context provider to <Provider> and the corresponding ` + `React context consumer to ${displayName} in connect options.` ) const { storeState, store } = value
let wrapperProps = this.props let forwardedRef
if (forwardRef) { wrapperProps = this.props.wrapperProps forwardedRef = this.props.forwardedRef } // 生成要将store中哪些数据注入props的方法 let derivedProps = this.selectDerivedProps( storeState, wrapperProps, store, selectorFactoryOptions ) // 拼装最后需要返回给connectAdvanced的组件 return this.selectChildElement( WrappedComponent, derivedProps, forwardedRef ) }
render() { // 获取context以便使用context.consumer获取store的变更 const ContextToUse = this.props.context && this.props.context.Consumer && isContextConsumer(<this.props.context.Consumer />) ? this.props.context : Context
return ( <ContextToUse.Consumer> {this.indirectRenderWrappedComponent} </ContextToUse.Consumer> ) } }
Connect.WrappedComponent = WrappedComponent Connect.displayName = displayName
if (forwardRef) { // 将ref转发给子组件 const forwarded = React.forwardRef(function forwardConnectRef( props, ref ) { return <Connect wrapperProps={props} forwardedRef={ref} /> })
forwarded.displayName = displayName forwarded.WrappedComponent = WrappedComponent // hoistStatics用于copy静态方法,避免在使用HOC的时候类的静态方法丢失 return hoistStatics(forwarded, WrappedComponent) }
return hoistStatics(Connect, WrappedComponent) } }
|