UNPKG

react-redux

Version:

Official React bindings for Redux

243 lines (207 loc) 7.68 kB
import type { Dispatch, Action } from 'redux' import type { ComponentType } from 'react' import verifySubselectors from './verifySubselectors' import type { EqualityFn, ExtendedEqualityFn } from '../types' export type SelectorFactory<S, TProps, TOwnProps, TFactoryOptions> = ( dispatch: Dispatch<Action<string>>, factoryOptions: TFactoryOptions, ) => Selector<S, TProps, TOwnProps> export type Selector<S, TProps, TOwnProps = null> = TOwnProps extends | null | undefined ? (state: S) => TProps : (state: S, ownProps: TOwnProps) => TProps export type MapStateToProps<TStateProps, TOwnProps, State> = ( state: State, ownProps: TOwnProps, ) => TStateProps export type MapStateToPropsFactory<TStateProps, TOwnProps, State> = ( initialState: State, ownProps: TOwnProps, ) => MapStateToProps<TStateProps, TOwnProps, State> export type MapStateToPropsParam<TStateProps, TOwnProps, State> = | MapStateToPropsFactory<TStateProps, TOwnProps, State> | MapStateToProps<TStateProps, TOwnProps, State> | null | undefined export type MapDispatchToPropsFunction<TDispatchProps, TOwnProps> = ( dispatch: Dispatch<Action<string>>, ownProps: TOwnProps, ) => TDispatchProps export type MapDispatchToProps<TDispatchProps, TOwnProps> = | MapDispatchToPropsFunction<TDispatchProps, TOwnProps> | TDispatchProps export type MapDispatchToPropsFactory<TDispatchProps, TOwnProps> = ( dispatch: Dispatch<Action<string>>, ownProps: TOwnProps, ) => MapDispatchToPropsFunction<TDispatchProps, TOwnProps> export type MapDispatchToPropsParam<TDispatchProps, TOwnProps> = | MapDispatchToPropsFactory<TDispatchProps, TOwnProps> | MapDispatchToProps<TDispatchProps, TOwnProps> export type MapDispatchToPropsNonObject<TDispatchProps, TOwnProps> = | MapDispatchToPropsFactory<TDispatchProps, TOwnProps> | MapDispatchToPropsFunction<TDispatchProps, TOwnProps> export type MergeProps<TStateProps, TDispatchProps, TOwnProps, TMergedProps> = ( stateProps: TStateProps, dispatchProps: TDispatchProps, ownProps: TOwnProps, ) => TMergedProps interface PureSelectorFactoryComparisonOptions<TStateProps, TOwnProps, State> { readonly areStatesEqual: ExtendedEqualityFn<State, TOwnProps> readonly areStatePropsEqual: EqualityFn<TStateProps> readonly areOwnPropsEqual: EqualityFn<TOwnProps> } function pureFinalPropsSelectorFactory< TStateProps, TOwnProps, TDispatchProps, TMergedProps, State, >( mapStateToProps: WrappedMapStateToProps<TStateProps, TOwnProps, State>, mapDispatchToProps: WrappedMapDispatchToProps<TDispatchProps, TOwnProps>, mergeProps: MergeProps<TStateProps, TDispatchProps, TOwnProps, TMergedProps>, dispatch: Dispatch<Action<string>>, { areStatesEqual, areOwnPropsEqual, areStatePropsEqual, }: PureSelectorFactoryComparisonOptions<TStateProps, TOwnProps, State>, ) { let hasRunAtLeastOnce = false let state: State let ownProps: TOwnProps let stateProps: TStateProps let dispatchProps: TDispatchProps let mergedProps: TMergedProps function handleFirstCall(firstState: State, firstOwnProps: TOwnProps) { state = firstState ownProps = firstOwnProps stateProps = mapStateToProps(state, ownProps) dispatchProps = mapDispatchToProps(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) hasRunAtLeastOnce = true return mergedProps } function handleNewPropsAndNewState() { stateProps = mapStateToProps(state, ownProps) if (mapDispatchToProps.dependsOnOwnProps) dispatchProps = mapDispatchToProps(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } function handleNewProps() { if (mapStateToProps.dependsOnOwnProps) stateProps = mapStateToProps(state, ownProps) if (mapDispatchToProps.dependsOnOwnProps) dispatchProps = mapDispatchToProps(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } function handleNewState() { const nextStateProps = mapStateToProps(state, ownProps) const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps) stateProps = nextStateProps if (statePropsChanged) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } function handleSubsequentCalls(nextState: State, nextOwnProps: TOwnProps) { const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps) const stateChanged = !areStatesEqual( nextState, state, nextOwnProps, ownProps, ) state = nextState ownProps = nextOwnProps if (propsChanged && stateChanged) return handleNewPropsAndNewState() if (propsChanged) return handleNewProps() if (stateChanged) return handleNewState() return mergedProps } return function pureFinalPropsSelector( nextState: State, nextOwnProps: TOwnProps, ) { return hasRunAtLeastOnce ? handleSubsequentCalls(nextState, nextOwnProps) : handleFirstCall(nextState, nextOwnProps) } } interface WrappedMapStateToProps<TStateProps, TOwnProps, State> { (state: State, ownProps: TOwnProps): TStateProps readonly dependsOnOwnProps: boolean } interface WrappedMapDispatchToProps<TDispatchProps, TOwnProps> { (dispatch: Dispatch<Action<string>>, ownProps: TOwnProps): TDispatchProps readonly dependsOnOwnProps: boolean } export interface InitOptions<TStateProps, TOwnProps, TMergedProps, State> extends PureSelectorFactoryComparisonOptions<TStateProps, TOwnProps, State> { readonly shouldHandleStateChanges: boolean readonly displayName: string readonly wrappedComponentName: string readonly WrappedComponent: ComponentType<TOwnProps> readonly areMergedPropsEqual: EqualityFn<TMergedProps> } export interface SelectorFactoryOptions< TStateProps, TOwnProps, TDispatchProps, TMergedProps, State, > extends InitOptions<TStateProps, TOwnProps, TMergedProps, State> { readonly initMapStateToProps: ( dispatch: Dispatch<Action<string>>, options: InitOptions<TStateProps, TOwnProps, TMergedProps, State>, ) => WrappedMapStateToProps<TStateProps, TOwnProps, State> readonly initMapDispatchToProps: ( dispatch: Dispatch<Action<string>>, options: InitOptions<TStateProps, TOwnProps, TMergedProps, State>, ) => WrappedMapDispatchToProps<TDispatchProps, TOwnProps> readonly initMergeProps: ( dispatch: Dispatch<Action<string>>, options: InitOptions<TStateProps, TOwnProps, TMergedProps, State>, ) => MergeProps<TStateProps, TDispatchProps, TOwnProps, TMergedProps> } // TODO: Add more comments // The selector returned by selectorFactory will memoize its results, // allowing connect's shouldComponentUpdate to return false if final // props have not changed. export default function finalPropsSelectorFactory< TStateProps, TOwnProps, TDispatchProps, TMergedProps, State, >( dispatch: Dispatch<Action<string>>, { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }: SelectorFactoryOptions< TStateProps, TOwnProps, TDispatchProps, TMergedProps, State >, ) { const mapStateToProps = initMapStateToProps(dispatch, options) const mapDispatchToProps = initMapDispatchToProps(dispatch, options) const mergeProps = initMergeProps(dispatch, options) if (process.env.NODE_ENV !== 'production') { verifySubselectors(mapStateToProps, mapDispatchToProps, mergeProps) } return pureFinalPropsSelectorFactory< TStateProps, TOwnProps, TDispatchProps, TMergedProps, State >(mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options) }