Definitelytyped: Некоторые существующие вызовы подключения прерываются 5.0.16

Созданный на 11 апр. 2018  ·  3Комментарии  ·  Источник: DefinitelyTyped/DefinitelyTyped

  • [x] Я пробовал использовать пакет @types/xxxx , и у меня возникли проблемы.
  • [x] Я пробовал использовать последнюю стабильную версию tsc. https://www.npmjs.com/package/typescript
  • [x] У меня вопрос, который не подходит для StackOverflow . (Пожалуйста, задавайте там любые уместные вопросы).
  • [x] [Упоминание] (https://github.com/blog/821-mention-somebody-they-re-notified) авторов (см. Definitions by: в index.d.ts ), чтобы они могли реагировать.

    • Авторы: @Pajn @tkqubo @thasner @kenzierocks @ Clayne11 @tansongyang @NicholasBoll @mDibyo @pdeva

Проблемы)

Привет! Я только что обновился до 5.0.16 и сразу же столкнулся с некоторыми проблемами при использовании этих новых типов в моем существующем проекте. Вот некоторые соответствующие номера версий:

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

Типичный контейнер не компилируется

Вот код, который, как мне кажется, должен компилироваться (и который компилировался до 5.0.16 и который компилируется снова, если мы вернем версию к 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

Обычно я бы использовал такой контейнер,

<ExampleContainer reportType={ReportType.USERS_CREATED} />

Однако с этим кодом, используя последний @types/react-redux в 5.0.16 и машинописный текст в 2.8.1 я вместо этого получаю следующую ошибку:

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

При проверке изменений, внесенных в 5.0.16 , создается впечатление, что система типов расстроена, потому что connector имеет тип InferableComponentEnhancerWithProps<IPropsFromState & IPropsFromParent, IPropsFromParent> что означает, что он ожидает, что первый аргумент будет иметь тип IPropsFromState & IPropsFromParent . В моем базовом компоненте отсутствует свойство reportType .

Например, если я напишу это:

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)

Эти минимальные примеры терпят неудачу. Первый не работает по той же причине, что и код выше, с тем же непрозрачным сообщением об ошибке. Второй не работает, потому что, очевидно, IExampleProps не имеет свойства reportType: ReportType поэтому мы не можем присвоить IExampleProps P . Отсутствует reportType . Я подозреваю, что это скрытая причина того, что первый пример потерпел неудачу.

Похоже, что после того, как 5.0.16 контейнеры

Это часть моего обычного рабочего процесса - определять компоненты только на основе того, какие свойства им нужны, а затем расширять эти компоненты с помощью HOC, которые определяют новый интерфейс и предоставляют компоненту его свойства через хранилище redux. В некоторых случаях новый интерфейс является подмножеством интерфейса обернутого компонента, но в других случаях это может быть не так. В приведенном выше случае новый интерфейс имеет дополнительное свойство reportType которое вызывающий будет использовать, но который обернутый компонент никогда не увидит.


Более идиосинкразический контейнер не компилируется

У меня есть еще один пример техники, которую я использую, которая не работает из-за этого изменения:

Предположим, у нас есть два простых компонента,

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

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

Я хочу создать HOC, который предоставляет date для этих компонентов без необходимости вообще ничего знать о свойстве name . Таким образом я могу создать, например, NameProvider и DateProvider, и составить их так: NameProvider(DateProvider(BaseComponent)) или как этот DateProvider(NameProvider(BaseComponent)) , то, что я обычно не могу сделать с хорошо типизированным компонентом Enhancer из-за необходимости указывать 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
}

Этот новый модуль расширения компонентов, не зависящий от интерфейса, используется следующим образом:

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

.
.
.

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

Таким образом, DateProvider HOC может расширять компонент, не зная полностью об интерфейсе этого компонента. Ему нужно только знать, что данный компонент будет способен принимать предоставляемые им реквизиты.

С 5.0.16 это больше не работает, вместо этого я получаю следующее сообщение об ошибке:

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

Поигрывая, я заметил следующее:

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

Когда мы явно набираем коннектор, все работает?


Вот и все! Спасибо за чтение;) сейчас я установил свою @types/react-redux на предыдущую версию, и все в порядке.

Самый полезный комментарий

Возврат к 5.0.15 полностью не решает проблему. Хотя 5.0.15 будет компилировать такой код без предупреждения, он все равно будет ошибкой, если вы добавите проверку типов свойств для презентационного компонента в вызове connect . (Предполагая, что ваш компонент упаковки имеет опору, которой нет в MyComponent )

Например, это будет работать в 5.0.15, но не в 5.0.16.
connect(stateToProps, dispatchToProps)(MyComponent)

Однако даже в 5.0.15 вы не можете проверить типы реквизитов MyComponent без ошибки компилятора.
connect<statetoprops, dispatchtoprops, mycomponentprops)(stateToProps, dispatchToProps)(MyComponent)
Это приведет к ошибке с жалобой на то, что опора компонентов упаковки не существует в MyComponent

Я думаю, что это более серьезная проблема, которая скрывается в сопоставлениях типов, @Pajn .

Все 3 Комментарий

@variousauthors, не могли бы вы поделиться версиями @types/react и @types/react-redux вы используете?

РЕДАКТИРОВАТЬ: @ испортил вещи, а имена библиотек не появились, извините: разочарован:

@variousauthors Я думаю, вы попали в точку с этим комментарием

Похоже, что после 5.0.16 контейнерам не разрешено добавлять новые свойства к интерфейсам их обернутых компонентов? Вот что я вижу, глядя на код.

Ошибка в том, что определения типов для функции redux connect пересекаются с ownProps и mapPropsToState . Это означает, что любой компонент «контейнер» вынужден реплицировать свои свойства в любых производных «презентационных» компонентах, что нарушает общие шаблоны проектирования redux.

PR, который представил: https://github.com/DefinitiTyped/DefinitiTyped/pull/24764
Другая проблема: https://github.com/DefinitiTyped/DefinitiTyped/issues/24922

Возврат к 5.0.15 полностью не решает проблему. Хотя 5.0.15 будет компилировать такой код без предупреждения, он все равно будет ошибкой, если вы добавите проверку типов свойств для презентационного компонента в вызове connect . (Предполагая, что ваш компонент упаковки имеет опору, которой нет в MyComponent )

Например, это будет работать в 5.0.15, но не в 5.0.16.
connect(stateToProps, dispatchToProps)(MyComponent)

Однако даже в 5.0.15 вы не можете проверить типы реквизитов MyComponent без ошибки компилятора.
connect<statetoprops, dispatchtoprops, mycomponentprops)(stateToProps, dispatchToProps)(MyComponent)
Это приведет к ошибке с жалобой на то, что опора компонентов упаковки не существует в MyComponent

Я думаю, что это более серьезная проблема, которая скрывается в сопоставлениях типов, @Pajn .

Была ли эта страница полезной?
0 / 5 - 0 рейтинги