Definitelytyped: Algunas llamadas de conexión existentes están interrumpidas por 5.0.16

Creado en 11 abr. 2018  ·  3Comentarios  ·  Fuente: DefinitelyTyped/DefinitelyTyped

  • [x] Intenté usar el paquete @types/xxxx y tuve problemas.
  • [x] Intenté usar la última versión estable de tsc. https://www.npmjs.com/package/typescript
  • [x] Tengo una pregunta que no es apropiada para StackOverflow . (Haga las preguntas pertinentes allí).
  • [x] [Mencione] (https://github.com/blog/821-mention-somebody-they-re-notified) a los autores (consulte Definitions by: en index.d.ts ) para que puedan responder.

    • Autores: @Pajn @tkqubo @thasner @kenzierocks @ clayne11 @tansongyang @NicholasBoll @mDibyo @pdeva

Los problemas)

¡Hola! Acabo de actualizar a 5.0.16 e inmediatamente encontré algunos problemas al usar estas nuevas mecanografías en mi proyecto existente. A continuación, se muestran algunos números de versión relevantes:

@types/react-redux: ^5.0.16,
typescript: ^2.8.x,
react-redux: ^5.0.5,

Un contenedor típico no se compila

Aquí hay un código que creo que debería compilarse (y que se compiló antes de 5.0.16 y que se compila de nuevo si volvemos a establecer la versión en 5.0.15 ):

// components/exampleComponent.ts
import React = require('react')
import { IState, IDateRange, ReportType } from '../../types/index'

export interface IExampleProps {
  label?: string
  dateRange: IDateRange
  onDateRangeChange: (value: IDateRange) => void
}

export class ExampleComponent extends React.Component<IExampleProps> {
  // implementation
}
// containers/exampleComponent.ts
import { connect } from 'react-redux'
import { IState, IDateRange, ReportType } from '../../types/index'

interface IPropsFromState {
  dateRange: IDateRange
}

interface IPropsFromParent {
  reportType: ReportType
}

const mapStateToProps = (state: IState, props: IPropsFromParent): IPropsFromState => {
  const config = state.reporting.reportConfigs[props.reportType]

  return {
    dateRange: config ? config.dateRange : null,
  }
}

const connector = connect<IPropsFromState, null, IPropsFromParent>(
  mapStateToProps,
)

export const ExampleContainer = connector(ExampleComponent) // <-- this does not compile

Normalmente seguiría usando el contenedor así,

<ExampleContainer reportType={ReportType.USERS_CREATED} />

Sin embargo, con este código, usando el último @types/react-redux en 5.0.16 y mecanografiado en 2.8.1 , obtengo el siguiente error:

[ts]
Argument of type 'typeof ExampleComponent' is not assignable to parameter of type 'ComponentType<IPropsFromState & DispatchProp<any> & IPropsFromParent>'.
  Type 'typeof ExampleComponent' is not assignable to type 'StatelessComponent<IPropsFromState & DispatchProp<any> & IPropsFromParent>'.
    Type 'typeof ExampleComponent' provides no match for the signature '(props: IPropsFromState & DispatchProp<any> & IPropsFromParent & { children?: ReactNode; }, context?: any): ReactElement<any>'.

Al inspeccionar los cambios introducidos en 5.0.16 , parece que el sistema de tipos está alterado porque connector tiene el tipo InferableComponentEnhancerWithProps<IPropsFromState & IPropsFromParent, IPropsFromParent> que significa que espera que el primer argumento sea del tipo IPropsFromState & IPropsFromParent . A mi componente base le falta la propiedad reportType .

Por ejemplo, si escribo esto:

const f = <P extends IPropsFromParent & IPropsFromState>(component: Component<P>) => { /**/ }
const g = <P extends IPropsFromParent & IPropsFromState>(props: P) => { /**/ }

const record: IExampleProps = null

f(ExampleComponent)
g(record)

Estos ejemplos mínimos fallan. El primero falla por la misma razón que el código anterior, con el mismo mensaje de error opaco. El segundo falla porque, claramente, IExampleProps no tiene una propiedad reportType: ReportType por lo que no podemos asignar IExampleProps a P . Falta reportType . Sospecho que esto es, bajo el capó, lo que está causando que falle el primer ejemplo.

¿Parece que después de 5.0.16 contenedores no pueden agregar nuevas propiedades a las interfaces de sus componentes empaquetados? Esto es lo que veo, mirando el código.

Es parte de mi flujo de trabajo normal definir los componentes en términos solo de las propiedades que necesitan, y luego extender esos componentes con HOC que definen una nueva interfaz y proporcionan al componente sus propiedades a través de la tienda redux. En algunos casos, la nueva interfaz es un subconjunto de la interfaz del componente envuelto, pero en otros casos puede que no lo sea. En el caso anterior, la nueva interfaz tiene una propiedad adicional reportType que la persona que llama usará pero que el componente envuelto nunca verá.


Un contenedor más idiosincrásico no se compila

Tengo otro ejemplo de una técnica que uso que está rota por este cambio:

Supongamos que tenemos dos componentes simples,

class NameDateComponent extends React.PureComponent<{ name: string, date: string}> {}

class DateOnlyComponent extends React.PureComponent<{ date: string }> {}

Quiero construir un HOC que proporcione date para estos componentes sin necesidad de saber nada sobre la propiedad name . De esta manera puedo hacer, por ejemplo, un NameProvider y un DateProvider, y componerlos así: NameProvider(DateProvider(BaseComponent)) o así DateProvider(NameProvider(BaseComponent)) , algo que normalmente no puedo hacer con un componente bien escrito. potenciador debido a la necesidad de especificar TOwnProps .

interface IWithDate {
  date: string
}

// ComponenProps defines a component interface that includes IWithDate
// NewAPI defines a new interface that excludes IWithDate
// so the types represent only what will change in the given component, without needing to know the rest of the interface
function DateProvider <ComponentProps extends IWithDate, NewAPI extends Minus<ComponentProps, IWithDate>> (Base: CompositeComponent<ComponentProps>) {

  const enhancer = connect<ComponentProps, null, NewAPI>(
    (_state: IState, props: NewAPI): ComponentProps => {
      // because of the 'never' type, 
      // typescript doesn't think NewAPI & IWithProps == ComponentProps
      // so we have to coerce this (but you and I can see that the above holds)
      const newProps: any = Object.assign({
        date: '2017-01-01'
      }, props)

      return newProps as ComponentProps
    },
    null
  )

  return enhancer(Base) // <-- after 5.0.16 this line does not compile
}

Este nuevo potenciador de componentes independiente de la interfaz se utiliza de la siguiente manera:

const NameOnly = DateProvider(NameDateComponent)
const Nothing = DateProvider(DateOnlyComponent)

.
.
.

<Nothing />
<NameOnly name='Bob' />
<NameDateComponent name='Bob' date='2017-01-01' />

Por lo tanto, el DateProvider HOC puede aumentar un componente sin ser plenamente consciente de la interfaz de ese componente. Solo necesita saber que el componente dado será capaz de aceptar los accesorios que proporciona.

Con 5.0.16 esto ya no funciona, en su lugar aparece el siguiente mensaje de error:

[ts]
Argument of type 'CompositeComponent<ComponentProps>' is not assignable to parameter of type 'ComponentType<ComponentProps & NewAPI>'.
  Type 'ComponentClass<ComponentProps>' is not assignable to type 'ComponentType<ComponentProps & NewAPI>'.
    Type 'ComponentClass<ComponentProps>' is not assignable to type 'StatelessComponent<ComponentProps & NewAPI>'.
      Types of property 'propTypes' are incompatible.
        Type 'ValidationMap<ComponentProps>' is not assignable to type 'ValidationMap<ComponentProps & NewAPI>'.
          Type 'keyof ComponentProps | keyof NewAPI' is not assignable to type 'keyof ComponentProps'.
            Type 'keyof NewAPI' is not assignable to type 'keyof ComponentProps'.
              Type 'keyof NewAPI' is not assignable to type '"date"'.

Jugando, he notado lo siguiente:

interface IWithDate {
  date: string
}

interface IComponentProps {
  name: string
  date: string
}

// I've torn out the internals of the DateProvider above
const connect1 = <T extends IWithDate, U extends Omit<T, keyof IWithDate>>(base: CompositeComponent<T>) => {
  const enhancer: InferableComponentEnhancerWithProps<T & U, U> = () => null

  return enhancer(base) // <-- this is a compile error
}

const connect2 = <T extends IWithDate, U extends Omit<T, keyof IWithDate>>() => {
  const enhancer: InferableComponentEnhancerWithProps<T & U, U> = () => null

  return enhancer
}

const enhancer = connect2<IComponentProps, Omit<IComponentProps, keyof IWithDate>>()
const container = enhancer(NameDateComponent) // <-- this is not a compile error

Cuando escribimos explícitamente el conector, ¿todo funciona?


¡Eso es todo! Gracias por leer;) por ahora he configurado mi @types/react-redux a una versión anterior y todo está bien.

Comentario más útil

Revertir a 5.0.15 no resuelve completamente el problema. Si bien 5.0.15 compilará este tipo de código sin previo aviso, todavía hay errores si agrega la verificación de tipo de los accesorios para el componente de presentación en la llamada connect . (Suponiendo que su componente de envoltura tiene un accesorio que MyComponent no tiene)

Por ejemplo, esto funcionará en 5.0.15, pero no en 5.0.16
connect(stateToProps, dispatchToProps)(MyComponent)

Sin embargo, incluso en la versión 5.0.15, no se pueden marcar las propiedades de MyComponent sin un error del compilador.
connect<statetoprops, dispatchtoprops, mycomponentprops)(stateToProps, dispatchToProps)(MyComponent)
Esto generará un error al quejarse de que la propiedad de los componentes de envoltura no existe en MyComponent

Creo que este es un problema mayor que ha estado al acecho en las asignaciones de tipos que la limpieza de @Pajn sacó a la luz.

Todos 3 comentarios

@variousauthors, ¿ podrías compartir las versiones de @types/react y @types/react-redux que estás usando?

EDITAR: el @ arruinó las cosas y los nombres de las bibliotecas no aparecieron, lo siento: decepcionado:

@variousauthors Creo que estás

¿Parece que después de 5.0.16 los contenedores no pueden agregar nuevas propiedades a las interfaces de sus componentes empaquetados? Esto es lo que veo, mirando el código.

El error es que las definiciones de tipo para la función connect de redux se cruzan con ownProps y mapPropsToState . Esto significa que cualquier componente de "contenedor" se ve obligado a tener sus accesorios replicados en cualquier componente de "presentación" derivado, lo que rompe los patrones comunes de diseño de redux.

PR que introdujo: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/24764
Otro problema: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/24922

Revertir a 5.0.15 no resuelve completamente el problema. Si bien 5.0.15 compilará este tipo de código sin previo aviso, todavía hay errores si agrega la verificación de tipo de los accesorios para el componente de presentación en la llamada connect . (Suponiendo que su componente de envoltura tiene un accesorio que MyComponent no tiene)

Por ejemplo, esto funcionará en 5.0.15, pero no en 5.0.16
connect(stateToProps, dispatchToProps)(MyComponent)

Sin embargo, incluso en la versión 5.0.15, no se pueden marcar las propiedades de MyComponent sin un error del compilador.
connect<statetoprops, dispatchtoprops, mycomponentprops)(stateToProps, dispatchToProps)(MyComponent)
Esto generará un error al quejarse de que la propiedad de los componentes de envoltura no existe en MyComponent

Creo que este es un problema mayor que ha estado al acecho en las asignaciones de tipos que la limpieza de @Pajn sacó a la luz.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

fasatrix picture fasatrix  ·  3Comentarios

jgoz picture jgoz  ·  3Comentarios

jbreckmckye picture jbreckmckye  ·  3Comentarios

Loghorn picture Loghorn  ·  3Comentarios

demisx picture demisx  ·  3Comentarios