reactjs - En TypeScript, cuando una propiedad del Componente proviene de connect, ¿cómo puedo usar el Componente?
entorno
- typescript 2.8
- react 16
- redux
Foo.tsx
interface IFooProps{
userId:number
}
class Foo extends Component<IFooProps>{
render(){
return <p>foo...</p>
}
}
const mapStateToProps = (state: IState) => {
return {
userId: state.user.id,
}
}
const mapDispatchToProps = (dispatch: Dispatch) => {
return {}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Foo)
Index.tsx
el siguiente código produjo un error de props userId faltante
// missing userId props error
<Foo />
lo que quiero:
Las props provienen de mapStateToProps, no es necesario pasarlas de nuevo.
Respuesta #1
Tu problema es un problema común que la gente encuentra al cambiar de JS a TS con React y usar HOC como connect con su componente.
Básicamente, con tu interfaz IFooProps, le dices a TS que userId es una propiedad requerida cada vez que se usa el componente y TS no sabe que en realidad, userId será manejado/inyectado en las propiedades a través de connect.
Hay un par de formas de resolver el problema, la primera es hacer que userId sea un atributo opcional.
interface IFooProps{
userId?: number
}
Este enfoque tiene un inconveniente, ya que TS te pedirá que realices una verificación de null/undefined cada vez que uses userId. Sin embargo, es el más simple.
El otro enfoque es definir otra interfaz, en tu ejemplo, puede ser convencionalmente nombrada: IFooInjectedProps. Tu componente será así:
interface IFooProps {
// ....Whatever props that require by the component
}
// All injected props from the store
interface IFooInjectedProps extends IFooProps {
userId: number
}
class Foo extends Component<IFooProps> {
get injectedProps() {
// Cast this.props as injectedProps to bypass typechecking
return this.props as IFooInjectedProps;
}
render() {
// Get userId from the getter `injectedProps`
const { userId } = this.injectedProps;
return <p>foo...</p>
}
}
Respuesta #2
Después de algunos años trabajando con Typescript y Redux, logré encontrar mi solución definitiva para connect. Es un poco complicado
Primero, necesitas usar una característica de aumento de módulo de TypeScript. Te permite extender los tipos existentes. Lo usaríamos para extender los tipos de react-redux. Necesitas crear un archivo .ts en algún lugar de tu proyecto con el siguiente código:
import { Component, ComponentClass } from "react-redux";
declare module "react-redux" {
export interface InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> {
<P extends TInjectedProps>(component: Component<P>): ComponentClass<
Omit<P, keyof TInjectedProps> & TNeedsProps
> & {
WrappedComponent: Component<P>;
};
allProps: TInjectedProps & TNeedsProps;
}
}
Aquí añadimos a la interfaz existente InferableComponentEnhancerWithProps una propiedad allProps: TInjectedProps & TNeedsProps;, el resto del código es una reescritura de la declaración de la interfaz existente de las tipificaciones originales.
Después de crearlo, el compilador de TypeScript debería reconocer automáticamente la nueva definición y ahora estamos listos para volver a tus archivos
Foo.tsx
// you only define props that you would like to pass from the parent component, not the props that are injected from redux state
interface IFooProps {
testProp: any;
}
// IConnectProps are defined at the bottom
class Foo extends Component<IConnectProps>{
render() {
this.props.testProps;
this.props.userId;
return <p>foo...</p>
}
}
const mapStateToProps = (state: IState, props: IFooProps) => {
return {
userId: state.user.id,
...props,
}
}
const mapDispatchToProps = (dispatch: Dispatch) => {
return {}
}
const connectCreator = connect(
mapStateToProps,
mapDispatchToProps
);
// here we use our new typings, thanks to them typescript automatically merge
// props that we declared in mapStateToProps so the IConnectProps
// looks like {
// userId: string;
// testProp: any;
// }
type IConnectProps = typeof connectCreator.allProps;
export default connectCreator(Foo);
Index.tsx
// typescript only tells you to pass a testProp
<Foo testProp={5} />
