Búsqueda de sitios web

javascript - typescript + redux: excluir propiedades de redux en el componente padre


Estoy usando redux y typescript para mi aplicación web actual.

¿Cuál es la mejor práctica para definir las props de un componente que recibe acciones de redux a través de @connect, pero también props del componente padre? Ejemplo:

// mychild.tsx
export namespace MyChildComponent {

  export interface IProps {
    propertyFromParent: string;
    propertyFromRedux: string;  // !!!! -> This is the problem
    actionsPropertyFromRedux: typeof MyReduxActions;  // !!!! -> And this     
  }
}

@connect(mapStateToProps, mapDispatchToProps)
export class MyChildComponent extends React.Component <MyChildComponent.IProps, any> {

... react stuff

}

function mapStateToProps(state: RootState) {
  return {
    propertyFromRedux: state.propertyFromRedux
  };
}
function mapDispatchToProps(dispatch) {
  return {
    actionsPropertyFromRedux: bindActionCreators(MyReduxActions as any, dispatch)
  };
}




// myparent.tsx
export class MyParentComponent extends React.Component <MyParentComponent.IProps, any> {

... react stuff

    render(){
        // typescript complains, because I am not passing `propertyFromRedux`! 
        return <div><MyChildComponent propertyFromParent="yay" /></div>;
    }

}

Tal como lo veo, tengo 2 soluciones.

  1. Simplemente pasa las acciones y el estado a través de toda mi aplicación. Pero eso significaría que toda mi aplicación se vuelva a renderizar incluso cuando solo algún componente hijo pequeño tendría que cambiar. ¿O es la forma de Redux de escuchar en mi componente de nivel superior todos los cambios del store? Entonces tendría que escribir mucha lógica dentro de shouldComponentUpdate para propiedades que no son objetos planos.

  2. Establece el parámetro en IProps de MyChildComponent como opcional así:

-

// mychild.tsx
export namespace MyChildComponent {

  export interface IProps {
    propertyFromParent: string;
    propertyFromRedux?: typeof MyAction;  // This is the problem
  }
}

¿Hay otra manera? Ambas formas anteriores me parecen demasiado desordenadas.

Respuesta #1

Necesitas dividir tus props: necesitarás un tipo DispatchProps, StateProps y un tipo OwnProps. También tendrás que usar los genéricos de TypeScript con connect

  • DispatchProps son tus creadores de acciones.
  • StateProps son tus props de estado (obvio) - estos provienen de mapStateToProps - el tipo de retorno de esa función debería coincidir con este tipo.
  • Los OwnProps son props que son aceptadas (y quizás esperadas) por tu componente. Las props opcionales deben marcarse como opcionales en la interfaz.

La forma en que lo hago (sin decoradores, pero estoy seguro de que se aplica aquí) es

interface ComponentDispatchProps {
    doSomeAction: typeof someAction;
}

interface ComponentStateProps {
    somethingFromState: any;
}

interface ComponentOwnProps {
    somethingWhichIsRequiredInProps: any;
    somethingWhichIsNotRequiredInProps?: any;
}

// not necessary to combine them into another type, but it cleans up the next line
type ComponentProps = ComponentStateProps & ComponentDispatchProps & ComponentOwnProps;

class Component extends React.Component<ComponentProps, {}> {...}

function mapStateToProps(state, props) { 
    return { somethingFromState };
}

export default connect<ComponentStateProps, ComponentDispatchProps, ComponentOwnProps>(
    mapStateToProps,
    mapDispatchToProps
)(Component);

Creo que tienes que usar @connect<StateProps, DispatchProps, OwnProps>, lo cual decorará y devolverá una clase que acepta OwnProps.

Si miras la implementación de connect en TS

export declare function connect<TStateProps, TDispatchProps, TOwnProps>(...): ComponentDecorator<TStateProps & TDispatchProps, TOwnProps>

interface ComponentDecorator<TOriginalProps, TOwnProps> {
    (component: ComponentClass<TOriginalProps> | StatelessComponent<TOriginalProps>): ComponentClass<TOwnProps>;
}

connect<...> devuelve un ComponentDecorator que, cuando se le pasa el componente (en tu caso esto se hace de manera transparente al transpilar el decorador), independientemente de StateProps y DispatchProps, devuelve un componente que espera OwnProps.

connect (no genérico) devuelve InferableComponentDecorator

export interface InferableComponentDecorator {
    <P, TComponentConstruct extends (ComponentClass<P> | StatelessComponent<P>)>(component: TComponentConstruct): TComponentConstruct;
}

que intenta inferir las props basándose en las props suministradas al componente, que en tu caso es la combinación de todas las props (OwnProps se convierte en ComponentProps de arriba).

Artículos relacionados