Definitelytyped: Algumas chamadas de conexão existentes são interrompidas por 5.0.16

Criado em 11 abr. 2018  ·  3Comentários  ·  Fonte: DefinitelyTyped/DefinitelyTyped

  • [x] Tentei usar o pacote @types/xxxx e tive problemas.
  • [x] Tentei usar a versão estável mais recente do tsc. https://www.npmjs.com/package/typescript
  • [x] Eu tenho uma pergunta que é inadequada para StackOverflow . (Por favor, faça quaisquer perguntas apropriadas lá).
  • [x] [Mencione] (https://github.com/blog/821-mention-somebody-they-re-notified) os autores (veja Definitions by: em index.d.ts ) para que eles possam responder.

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

As questões)

Olá! Acabei de atualizar para 5.0.16 e imediatamente encontrei alguns problemas ao usar essas novas tipificações em meu projeto existente. Aqui estão alguns números de versões relevantes:

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

Um contêiner típico não compila

Aqui está algum código que eu acho que deveria compilar (e que foi compilado antes de 5.0.16 e que compila novamente se definirmos a versão de volta para 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, eu usaria o contêiner assim,

<ExampleContainer reportType={ReportType.USERS_CREATED} />

No entanto, com este código, usando o @types/react-redux mais recente em 5.0.16 e datilografado em 2.8.1 , recebo o seguinte erro:

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

Inspecionando as mudanças introduzidas em 5.0.16 , parece que o sistema de tipos está chateado porque connector tem o tipo InferableComponentEnhancerWithProps<IPropsFromState & IPropsFromParent, IPropsFromParent> que significa que ele espera que o primeiro argumento seja do tipo IPropsFromState & IPropsFromParent . Meu componente básico está faltando a propriedade reportType .

Por exemplo, se eu escrever isto:

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)

Esses exemplos mínimos falham. O primeiro falha pelo mesmo motivo do código acima, com a mesma mensagem de erro opaca. A segunda falha porque, claramente, IExampleProps não tem uma propriedade reportType: ReportType então não podemos atribuir IExampleProps a P . Está faltando reportType . Suspeito que seja isso, nos bastidores, o que está causando o fracasso do primeiro exemplo.

Parece que depois de 5.0.16 contêineres não têm permissão para adicionar novas propriedades às interfaces de seus componentes embalados? Isso é o que vejo, olhando para o código.

É parte do meu fluxo de trabalho normal definir componentes em termos apenas de quais propriedades eles precisam e, em seguida, estender esses componentes com HOCs que definem uma nova interface e fornecem ao componente suas propriedades por meio do armazenamento redux. Em alguns casos, a nova interface é um subconjunto da interface do componente empacotado, mas em outros casos pode não ser. No caso acima, a nova interface tem uma propriedade adicional reportType que o chamador usará, mas que o componente empacotado nunca verá.


Um contêiner mais idiossincrático não compila

Eu tenho outro exemplo de uma técnica que uso que é interrompida por esta mudança:

Suponha que temos dois componentes simples,

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

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

Quero construir um HOC que forneça date para esses componentes sem precisar saber nada sobre a propriedade name . Desta forma, posso fazer, por exemplo, um NameProvider e um DateProvider, e compô-los assim: NameProvider(DateProvider(BaseComponent)) ou assim DateProvider(NameProvider(BaseComponent)) , algo que normalmente não posso fazer com um componente bem digitado realçador devido à necessidade 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 novo intensificador de componente agnóstico de interface é usado da seguinte maneira:

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

.
.
.

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

Portanto, o DateProvider HOC é capaz de aumentar um componente sem estar totalmente ciente da interface desse componente. Ele só precisa saber que determinado componente será capaz de aceitar os adereços que fornece.

Com 5.0.16 isso não funciona mais; em vez disso, recebo a seguinte mensagem de erro:

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

Brincando, percebi o seguinte:

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

Quando digitamos explicitamente o conector, tudo funciona?


É isso! Obrigado por ler;) por agora eu configurei meu @types/react-redux para uma versão anterior e está tudo bem.

Comentários muito úteis

Reverter para 5.0.15 não resolve completamente o problema. Embora 5.0.15 compile este tipo de código sem avisar, ainda haverá erros se você adicionar a verificação de tipo dos adereços para o componente de apresentação na chamada connect . (Supondo que seu componente de embrulho tenha um suporte que MyComponent não tem)

Por exemplo, isso funcionará em 5.0.15, mas não 5.0.16
connect(stateToProps, dispatchToProps)(MyComponent)

No entanto, mesmo no 5.0.15, você não pode verificar os MyComponent props sem erro do compilador
connect<statetoprops, dispatchtoprops, mycomponentprops)(stateToProps, dispatchToProps)(MyComponent)
Haverá um erro reclamando que a propriedade dos componentes da embalagem não existe em MyComponent

Acho que esse é um problema maior que está escondido nos mapeamentos de tipo que a limpeza de @Pajn trouxe à luz.

Todos 3 comentários

@variousauthors poderia compartilhar as versões de @types/react e @types/react-redux você está usando?

EDIT: o @ bagunçou as coisas e os nomes das libs não apareceram, desculpe: desapontado:

@variousauthors , acho que você

Parece que após o 5.0.16 os contêineres não têm permissão para adicionar novas propriedades às interfaces de seus componentes embalados? Isso é o que vejo, olhando para o código.

O bug é que as definições de tipo para a função connect do redux cruzam ownProps e mapPropsToState . Isso significa que qualquer componente de "contêiner" é forçado a ter seus adereços replicados em qualquer componente de "apresentação" derivado - o que quebra os padrões de design redux comuns.

PR que introduziu: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/24764
Outro problema: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/24922

Reverter para 5.0.15 não resolve completamente o problema. Embora 5.0.15 compile este tipo de código sem avisar, ainda haverá erros se você adicionar a verificação de tipo dos adereços para o componente de apresentação na chamada connect . (Supondo que seu componente de embrulho tenha um suporte que MyComponent não tem)

Por exemplo, isso funcionará em 5.0.15, mas não 5.0.16
connect(stateToProps, dispatchToProps)(MyComponent)

No entanto, mesmo no 5.0.15, você não pode verificar os MyComponent props sem erro do compilador
connect<statetoprops, dispatchtoprops, mycomponentprops)(stateToProps, dispatchToProps)(MyComponent)
Haverá um erro reclamando que a propriedade dos componentes da embalagem não existe em MyComponent

Acho que esse é um problema maior que está escondido nos mapeamentos de tipo que a limpeza de @Pajn trouxe à luz.

Esta página foi útil?
0 / 5 - 0 avaliações