UNPKG

react-redux

Version:

Official React bindings for Redux

245 lines (211 loc) 7.27 kB
import type { Dispatch, Action } from 'redux' import verifySubselectors from './verifySubselectors' import type { DefaultRootState, EqualityFn } from '../types' export type SelectorFactory<S, TProps, TOwnProps, TFactoryOptions> = ( dispatch: Dispatch<Action>, 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 = DefaultRootState > = (state: State, ownProps: TOwnProps) => TStateProps export type MapStateToPropsFactory< TStateProps, TOwnProps, State = DefaultRootState > = ( initialState: State, ownProps: TOwnProps ) => MapStateToProps<TStateProps, TOwnProps, State> export type MapStateToPropsParam< TStateProps, TOwnProps, State = DefaultRootState > = | MapStateToPropsFactory<TStateProps, TOwnProps, State> | MapStateToProps<TStateProps, TOwnProps, State> | null | undefined export type MapDispatchToPropsFunction<TDispatchProps, TOwnProps> = ( dispatch: Dispatch<Action>, ownProps: TOwnProps ) => TDispatchProps export type MapDispatchToProps<TDispatchProps, TOwnProps> = | MapDispatchToPropsFunction<TDispatchProps, TOwnProps> | TDispatchProps export type MapDispatchToPropsFactory<TDispatchProps, TOwnProps> = ( dispatch: Dispatch<Action>, 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< TOwnProps, State = DefaultRootState > { areStatesEqual: EqualityFn<State> areOwnPropsEqual: EqualityFn<TOwnProps> areStatePropsEqual: EqualityFn<unknown> displayName: string } export function pureFinalPropsSelectorFactory< TStateProps, TOwnProps, TDispatchProps, TMergedProps, State = DefaultRootState >( mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State> & { dependsOnOwnProps: boolean }, mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps> & { dependsOnOwnProps: boolean }, mergeProps: MergeProps<TStateProps, TDispatchProps, TOwnProps, TMergedProps>, dispatch: Dispatch, { areStatesEqual, areOwnPropsEqual, areStatePropsEqual, }: PureSelectorFactoryComparisonOptions<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 // @ts-ignore stateProps = mapStateToProps!(state, ownProps) // @ts-ignore dispatchProps = mapDispatchToProps!(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) hasRunAtLeastOnce = true return mergedProps } function handleNewPropsAndNewState() { // @ts-ignore stateProps = mapStateToProps!(state, ownProps) if (mapDispatchToProps!.dependsOnOwnProps) // @ts-ignore dispatchProps = mapDispatchToProps(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } function handleNewProps() { if (mapStateToProps!.dependsOnOwnProps) // @ts-ignore stateProps = mapStateToProps!(state, ownProps) if (mapDispatchToProps.dependsOnOwnProps) // @ts-ignore dispatchProps = mapDispatchToProps(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } function handleNewState() { const nextStateProps = mapStateToProps(state, ownProps) const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps) // @ts-ignore 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) 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) } } export interface SelectorFactoryOptions< TStateProps, TOwnProps, TDispatchProps, TMergedProps, State = DefaultRootState > extends PureSelectorFactoryComparisonOptions<TOwnProps, State> { initMapStateToProps: ( dispatch: Dispatch, options: PureSelectorFactoryComparisonOptions<TOwnProps, State> ) => MapStateToPropsParam<TStateProps, TOwnProps, State> initMapDispatchToProps: ( dispatch: Dispatch, options: PureSelectorFactoryComparisonOptions<TOwnProps, State> ) => MapDispatchToPropsParam<TDispatchProps, TOwnProps> initMergeProps: ( dispatch: Dispatch, options: PureSelectorFactoryComparisonOptions<TOwnProps, 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 = DefaultRootState >( dispatch: Dispatch<Action>, { 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) } const selectorFactory = pureFinalPropsSelectorFactory return selectorFactory( // @ts-ignore mapStateToProps!, mapDispatchToProps, mergeProps, dispatch, options ) }