Definitelytyped: Certains appels de connexion existants sont interrompus par 5.0.16

Créé le 11 avr. 2018  ·  3Commentaires  ·  Source: DefinitelyTyped/DefinitelyTyped

  • [x] J'ai essayé d'utiliser le package @types/xxxx et j'ai eu des problèmes.
  • [x] J'ai essayé d'utiliser la dernière version stable de tsc. https://www.npmjs.com/package/typescript
  • [x] J'ai une question inappropriée pour StackOverflow . (Veuillez y poser toutes les questions appropriées).
  • [x] [Mention](https://github.com/blog/821-mention-somebody-they-re-notified) les auteurs (voir Definitions by: dans index.d.ts ) afin qu'ils puissent répondre.

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

Les problèmes)

Salut! Je viens de mettre à jour vers 5.0.16 et j'ai immédiatement rencontré des problèmes lors de l'utilisation de ces nouvelles saisies dans mon projet existant. Voici quelques numéros de version pertinents :

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

Un conteneur typique ne compile pas

Voici du code qui, selon moi, devrait être compilé (et qui a été compilé avant 5.0.16 et qui se compile à nouveau si nous remettons la version à 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

Normalement, je continuerais à utiliser le conteneur comme celui-ci,

<ExampleContainer reportType={ReportType.USERS_CREATED} />

Cependant, avec ce code, en utilisant le dernier @types/react-redux à 5.0.16 et tapscript à 2.8.1 j'obtiens à la place l'erreur suivante :

[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>'.

En inspectant les changements introduits dans 5.0.16 , il semble que le système de types soit bouleversé car connector a le type InferableComponentEnhancerWithProps<IPropsFromState & IPropsFromParent, IPropsFromParent> ce qui signifie qu'il s'attend à ce que le premier argument soit de type IPropsFromState & IPropsFromParent . Il manque à mon composant de base la propriété reportType .

Par exemple, si j'écris ceci :

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)

Ces exemples minimes échouent. Le premier échoue pour la même raison que le code ci-dessus, avec le même message d'erreur opaque. La seconde échoue car, clairement, IExampleProps n'a pas de propriété reportType: ReportType donc nous ne pouvons pas affecter IExampleProps à P . Il manque reportType . Je soupçonne que c'est, sous le capot, ce qui fait échouer le premier exemple.

Il semble qu'après 5.0.16 conteneurs ne soient pas autorisés à ajouter de nouvelles propriétés aux interfaces de leurs composants encapsulés ? C'est ce que je vois en regardant le code.

Cela fait partie de mon flux de travail normal de définir les composants uniquement en fonction des propriétés dont ils ont besoin, puis d'étendre ces composants avec des HOC qui définissent une nouvelle interface et fournissent au composant ses propriétés via le magasin redux. Dans certains cas, la nouvelle interface est un sous-ensemble de l'interface du composant encapsulé, mais dans d'autres cas, cela peut ne pas l'être. Dans le cas ci-dessus, la nouvelle interface a une propriété supplémentaire reportType que l'appelant utilisera mais que le composant encapsulé ne verra jamais.


Un conteneur plus idiosyncratique ne compile pas

J'ai un autre exemple d'une technique que j'utilise qui est brisée par ce changement :

Supposons que nous ayons deux composants simples,

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

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

Je veux construire un HOC qui fournit le date pour ces composants sans avoir besoin de savoir quoi que ce soit sur la propriété name . De cette façon, je peux créer, par exemple, un NameProvider et un DateProvider, et les composer comme ceci : NameProvider(DateProvider(BaseComponent)) ou comme ceci DateProvider(NameProvider(BaseComponent)) , quelque chose que je ne peux normalement pas faire avec un composant bien typé Enhancer en raison de la nécessité de spécifier 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
}

Ce nouvel amplificateur de composant indépendant de l'interface est utilisé comme suit :

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

.
.
.

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

Ainsi, le DateProvider HOC est capable d'augmenter un composant sans être pleinement conscient de l'interface de ce composant. Il a seulement besoin de savoir que le composant donné sera capable d'accepter les accessoires qu'il fournit.

Avec 5.0.16 cela ne fonctionne plus, à la place j'obtiens le message d'erreur suivant :

[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"'.

En jouant, j'ai remarqué ce qui suit :

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

Quand on tape explicitement le connecteur, tout fonctionne ?


C'est ça! Merci d'avoir lu ;) pour l'instant j'ai mis mon @types/react-redux sur une version précédente et tout va bien.

Commentaire le plus utile

Revenir à 5.0.15 ne résout pas complètement le problème. Alors que 5.0.15 compilera ce type de code sans avertissement, il y aura toujours des erreurs si vous ajoutez la vérification de type des accessoires pour le composant de présentation dans l'appel connect . (En supposant que votre composant d'emballage a un accessoire que MyComponent n'a pas)

Par exemple, cela fonctionnera dans 5.0.15, mais pas dans 5.0.16
connect(stateToProps, dispatchToProps)(MyComponent)

Cependant, même dans 5.0.15, vous ne pouvez pas tapercheck les accessoires MyComponent sans erreur du compilateur
connect<statetoprops, dispatchtoprops, mycomponentprops)(stateToProps, dispatchToProps)(MyComponent)
Cela provoquera une erreur en se plaignant que la prop des composants d'emballage n'existe pas dans MyComponent

Je pense qu'il s'agit d'un problème plus important qui se cache dans les mappages de types que le nettoyage de @Pajn a mis en lumière.

Tous les 3 commentaires

@diversauteurs pourriez-vous s'il vous plaît partager les versions de @types/react et @types/react-redux vous utilisez ?

EDIT: les @ foiré les choses et les noms des libs n'apparaissaient pas, désolé :déçu:

@diversauthors Je pense que vous avez raison avec ce commentaire

Il semble qu'après la version 5.0.16, les conteneurs ne soient plus autorisés à ajouter de nouvelles propriétés aux interfaces de leurs composants encapsulés ? C'est ce que je vois en regardant le code.

Le bogue est que les définitions de type pour la fonction connect de redux coupent ownProps et mapPropsToState . Cela signifie que tout composant "conteneur" est obligé d'avoir ses accessoires répliqués dans tous les composants "présentatifs" dérivés - ce qui casse les modèles de conception de redux communs.

PR qui a introduit : https://github.com/DefinitelyTyped/DefinitelyTyped/pull/24764
Autre problème : https://github.com/DefinitelyTyped/DefinitelyTyped/issues/24922

Revenir à 5.0.15 ne résout pas complètement le problème. Alors que 5.0.15 compilera ce type de code sans avertissement, il y aura toujours des erreurs si vous ajoutez la vérification de type des accessoires pour le composant de présentation dans l'appel connect . (En supposant que votre composant d'emballage a un accessoire que MyComponent n'a pas)

Par exemple, cela fonctionnera dans 5.0.15, mais pas dans 5.0.16
connect(stateToProps, dispatchToProps)(MyComponent)

Cependant, même dans 5.0.15, vous ne pouvez pas tapercheck les accessoires MyComponent sans erreur du compilateur
connect<statetoprops, dispatchtoprops, mycomponentprops)(stateToProps, dispatchToProps)(MyComponent)
Cela provoquera une erreur en se plaignant que la prop des composants d'emballage n'existe pas dans MyComponent

Je pense qu'il s'agit d'un problème plus important qui se cache dans les mappages de types que le nettoyage de @Pajn a mis en lumière.

Cette page vous a été utile?
0 / 5 - 0 notes