Как исправить TS2322: мог ли быть создан экземпляр с другим подтипом ограничения «объект»?

A имеют ошибку проверки типа в рекурсивных типах.

Я пытаюсь написать типы для объекта стилей response-jss.

type StylesFn<P extends object> = (
  props: P
) => CSS.Properties<JssValue<P>> | number | string;

type JssValue<P extends object> =
  | string
  | number
  | Array<string | number>
  | StylesFn<P>;

// @ts-ignore
interface StylesObject<K extends string = any, P extends object = {}>
  extends Styles {
  [x: string]: CSS.Properties<JssValue<P>> | Styles<K, P>;
}
export type Styles<K extends string = any, P extends object = {}> = {
  [x in K]: CSS.Properties<JssValue<P>> | StylesObject<any, P> | StylesFn<P>
};

Работает нормально, но машинопись пишет ошибку. Я использую @ts-ignore, но это не прикол

ERROR 24:11  typecheck  Interface 'StylesObject<K, P>' incorrectly extends interface 'Styles<any, {}>'.
  Index signatures are incompatible.
    Type 'Properties<JssValue<P>> | Styles<K, P>' is not assignable to type 'StylesFn<{}> | Properties<JssValue<{}>> | StylesObject<any, {}>'.
      Type 'Properties<JssValue<P>>' is not assignable to type 'StylesFn<{}> | Properties<JssValue<{}>> | StylesObject<any, {}>'.
        Type 'Properties<JssValue<P>>' is not assignable to type 'Properties<JssValue<{}>>'.
          Type 'JssValue<P>' is not assignable to type 'JssValue<{}>'.
            Type 'StylesFn<P>' is not assignable to type 'JssValue<{}>'.
              Type 'StylesFn<P>' is not assignable to type 'StylesFn<{}>'.
                Type '{}' is not assignable to type 'P'.
                  '{}' is assignable to the constraint of type 'P', but 'P' could be instantiated with a different subtype of constraint 'object'.

Что означает эта ошибка?


person teux    schedule 08.06.2019    source источник
comment
Это то же сообщение об ошибке, что и в этом вопросе, на который можно частично ответить в комментариях.   -  person ChrisW    schedule 21.06.2019


Ответы (3)


Дополняя @fetzz отличным ответом.


КОРОТКИЙ ОТВЕТ

TL; DR; Есть две распространенные причины сообщения об ошибке такого типа. Вы делаете первый (см. Ниже). Наряду с текстом я подробно объясняю, что хочет передать это сообщение об ошибке.

ПРИЧИНА 1. В машинописном тексте конкретный экземпляр не может быть назначен параметру типа. Ниже вы можете увидеть примеры «проблемы» и «решенной проблемы», чтобы вы могли сравнить разницу и увидеть, какие изменения:

ПРОБЛЕМА

const func1 = <A extends string>(a: A = 'foo') => `hello!` // Error!

const func2 = <A extends string>(a: A) => {
    //stuff
    a = `foo`  // Error!
    //stuff
}

РЕШЕНИЕ

const func1 = <A extends string>(a: A) => `hello!` // ok

const func2 = <A extends string>(a: A) => { //ok
    //stuff
    //stuff
}

См в: Площадка для TS

ПРИЧИНА 2: хотя в вашем коде не происходит ошибка, указанная ниже. Это также нормальная ситуация, когда появляется такое сообщение об ошибке. Вам следует избегать этого:

Повторить (по ошибке) Type Parameter в классе, типе или интерфейсе.

Не позволяйте сложности приведенного ниже кода сбивать вас с толку, единственное, на чем я хочу, чтобы вы сосредоточились, - это то, как удаление буквы «А» решает проблему:

ПРОБЛЕМА:

type Foo<A> = {
    //look the above 'A' is conflicting with the below 'A'
    map: <A,B>(f: (_: A) => B) => Foo<B>
}

const makeFoo = <A>(a: A): Foo<A> => ({
   map: f => makeFoo(f(a)) //error!
})

РЕШЕНИЕ:

type Foo<A> = {
    // conflict removed
    map: <B>(f: (_: A) => B) => Foo<B>
}

const makeFoo = <A>(a: A): Foo<A> => ({
   map: f => makeFoo(f(a)) //ok
})

См: TS площадка


ДЛИННЫЙ ОТВЕТ


ПОНЯТИЕ СООБЩЕНИЯ ОБ ОШИБКЕ

Далее я разложу каждый элемент сообщения об ошибке ниже:

Type '{}' is not assignable to type 'P'.
  '{}' is assignable to the constraint of type 'P', but 'P' could be
 instantiated with a different subtype of constraint'object'

ЧТО ТАКОЕ ТИП {}

Это тип, которому вы можете присвоить что угодно, кроме null или undefined. Например:

type A = {}
const a0: A = undefined // error
const a1: A = null // error
const a2: A = 2 // ok
const a3: A = 'hello world' //ok
const a4: A = { foo: 'bar' } //ok
// and so on...

См: TS площадка


ЧТО ТАКОЕ is not assignable

Назначить - значит сделать переменную определенного типа, соответствующей конкретному экземпляру. Если вы не соответствуете типу экземпляра, вы получите ошибку. Например:

// type string is not assignable to type number 
const a: number = 'hello world' //error

// type number is assinable to type number
const b: number = 2 // ok


ЧТО ТАКОЕ different subtype

Два типа равны: если они не добавляют и не удаляют детали по отношению друг к другу.

Два типа разные: если они не равны.

Тип A является подтипом типа S: если A добавляет деталь, без удаления уже существующей детали из S.

тип A и тип B - разные подтипы типа S: если A и B являются подтипами S, но A и B - разные типы. Другими словами: A и B добавляют детали к типу S, но они не добавляют одинаковых деталей.

Пример. В приведенном ниже коде верны все следующие утверждения:

  1. A и D - одинаковые типы
  2. B - подтип A
  3. E не подтип A
  4. B и C - разные подтипы A
type A = { readonly 0: '0'}
type B = { readonly 0: '0', readonly foo: 'foo'}
type C = { readonly 0: '0', readonly bar: 'bar'}
type D = { readonly 0: '0'}
type E = { readonly 1: '1', readonly bar: 'bar'}
type A = number
type B = 2
type C = 7
type D = number
type E = `hello world`
type A = boolean
type B = true
type C = false
type D = boolean
type E = number

ПРИМЕЧАНИЕ: Структурный тип

Когда вы видите в TS использование ключевого слова type, например, в type A = { foo: 'Bar' }, вы должны прочитать: Псевдоним типа A указывает на структуру типа { foo: 'Bar' }.

Общий синтаксис: type [type_alias_name] = [type_structure].

Система типов Typescript просто проверяет [type_structure], а не [type_alias_name]. Это означает, что в TS нет разницы в проверке типов между следующими: type A = { foo: 'bar } и type B = { foo: 'bar' }. Для получения дополнительной информации см .: Официальный документ.


ЧТО ТАКОЕ constraint of type 'X'

Ограничение типа - это просто то, что вы помещаете справа от ключевого слова extends. В приведенном ниже примере Type Constraint - это «B».

const func = <A extends B>(a: A) => `hello!`

Читает: Ограничение типа "B" - это constraint of type 'A'


ПОЧЕМУ ПРОИСХОДИТ ОШИБКА

Для иллюстрации я покажу вам три случая. Единственное, что будет меняться в каждом случае, - это Type Constraint, больше ничего не изменится.

Я хочу, чтобы вы обратили внимание, что ограничение, которое Type Constraint налагает на Type Parameter , не включает различные подтипы. Давай увидим это:

Данный:

type Foo         =  { readonly 0: '0'}
type SubType     =  { readonly 0: '0', readonly a: 'a'}
type DiffSubType =  { readonly 0: '0', readonly b: 'b'}

const foo:             Foo         = { 0: '0'}
const foo_SubType:     SubType     = { 0: '0', a: 'a' }
const foo_DiffSubType: DiffSubType = { 0: '0', b: 'b' }

СЛУЧАЙ 1: БЕЗ ОГРАНИЧЕНИЙ

const func = <A>(a: A) => `hello!`

// call examples
const c0 = func(undefined) // ok
const c1 = func(null) // ok
const c2 = func(() => undefined) // ok
const c3 = func(10) // ok
const c4 = func(`hi`) // ok
const c5 = func({}) //ok
const c6 = func(foo) // ok
const c7 = func(foo_SubType) //ok
const c8 = func(foo_DiffSubType) //ok

СЛУЧАЙ 2: НЕКОТОРЫЕ ОГРАНИЧЕНИЯ

Обратите внимание, что ограничение не влияет на подтипы.

ОЧЕНЬ ВАЖНО: в Typescript Type Constraint не ограничивает разные подтипы

const func = <A extends Foo>(a: A) => `hello!`

// call examples
const c0 = func(undefined) // error
const c1 = func(null) // error
const c2 = func(() => undefined) // error
const c3 = func(10) // error
const c4 = func(`hi`) // error
const c5 = func({}) // error
const c6 = func(foo) // ok
const c7 = func(foo_SubType) // ok  <-- Allowed
const c8 = func(foo_DiffSubType) // ok <-- Allowed

СЛУЧАЙ 3: БОЛЬШЕ ОГРАНИЧЕНИЙ

const func = <A extends SubType>(a: A) => `hello!`

// call examples
const c0 = func(undefined) // error
const c1 = func(null) // error
const c2 = func(() => undefined) // error
const c3 = func(10) // error
const c4 = func(`hi`) // error
const c5 = func({}) // error
const c6 = func(foo) // error <-- Restricted now
const c7 = func(foo_SubType) // ok  <-- Still allowed
const c8 = func(foo_DiffSubType) // error <-- NO MORE ALLOWED !

См в TS площадка


ЗАКЛЮЧЕНИЕ

Функция ниже:

const func = <A extends Foo>(a: A = foo_SubType) => `hello!` //error!

Выдает это сообщение об ошибке:

Type 'SubType' is not assignable to type 'A'.
  'SubType' is assignable to the constraint of type 'A', but 'A'
could be instantiated with a different subtype of constraint 
'Foo'.ts(2322)

Поскольку Typescript выводит A из вызова функции, но в языке нет ограничений, ограничивающих вас на вызов функции с разными подтипами 'Foo'. Например, все приведенные ниже вызовы функции считаются действительными:

const c0 = func(foo)  // ok! type 'Foo' will be infered and assigned to 'A'
const c1 = func(foo_SubType) // ok! type 'SubType' will be infered
const c2 = func(foo_DiffSubType) // ok! type 'DiffSubType' will be infered

Следовательно, присвоение конкретного типа общему Type Parameter неверно, потому что в TS Type Parameter может всегда быть инстанцирован для некоторого произвольного другого подтипа.

Решение:

Никогда не назначайте конкретный тип параметру универсального типа, считайте его read-only! Вместо этого сделайте следующее:

const func = <A extends Foo>(a: A) => `hello!` //ok!

См TS площадка

person Flavio Vilante    schedule 16.12.2019
comment
‹A extends Foo› (a: A) = ›hello! не эквивалентно (a: Foo) =› hello!? - person Trevor Karjanis; 16.01.2020
comment
@TrevorKarjanis они немного отличаются, например: <A extends Foo>(a: A = foo) => 'hello!' выдает ошибку компиляции, а (a: Foo = foo) => 'hello' есть действующий код. Смотрите здесь. - person Flavio Vilante; 17.01.2020
comment
Здесь отличное объяснение :) Я хотел бы прояснить несколько моментов: 1. В «ЧТО ЕСТЬ Другой подтип» вы говорите, что Foo - это тип. SubFoo и DiffSubType - его подтипы. Но DiffSubType считается другим подтипом. Почему? SubFoo и DiffSubType реализуют одно и то же свойство prop1 и оба добавляют одно дополнительное свойство, разве они не должны быть просто подтипами Foo? 2. Почему в разделе «ЗАКЛЮЧЕНИЕ» в первом примере кода foo_SubType нельзя назначить ограничению A? foo_SubType, (скажем) имеет дополнительные props и prop1 такие же, как и тип foo_Type, так что назначить его должно быть нормально, верно? - person Max; 26.01.2020
comment
В качестве продолжения я наконец понял суть своего второго вопроса, потому что в const func = <A extends Foo>(a: A = foo_SubType) => 'hello!', func можно вызывать с другим подтипом, например: func<DiffSubType>(), поэтому присвоение a: A = foo_SubType невозможно, потому что теперь A теперь установлено в DiffSubType, что является несовместимый тип для foo_SubType, поэтому машинопись жалуется. И да, как вы сказали, может быть бесконечное количество подтипов определенного типа. Таким образом, невозможно узнать, можно ли присвоить foo_SubType DiffSubType в моем примере выше. Можете ли вы помочь мне с 1-м вопросом? - person Max; 26.01.2020
comment
@Max Спасибо. вы спросили: Why? SubFoo and DiffSubType both implement same prop1 and both add one additional prop, shouldn't they be both be just subtypes of Foo? Это просто подтипы Foo, вы не ошиблись. Возникает вопрос: учитывая, что они ЯВЛЯЮТСЯ подтипами, являются ли они одинаковыми или разными подтипами? Ответ: Разные, потому что они добавляют разные свойства / методы по отношению друг к другу. См. этот пример здесь (возможно, вы более знакомы с ключевым словом interface). - person Flavio Vilante; 27.01.2020
comment
Некоторые подсказки: Что касается понимания ошибки, сообщение всегда будет печатать {}, а не unknown. {} является здесь общим типом по умолчанию Styles, который применяется, потому что в StylesObject для Styles не задан общий аргумент. Также {} не является пустым типом, а скорее одним из основных типов в TS. Это означает, что вы можете присвоить {} практически все. Это примерно unkown минус null и undefined. - person ford04; 27.04.2020
comment
@ ford04 спасибо. Я внес изменения в ответ, чтобы отразить ваши комментарии. - person Flavio Vilante; 27.04.2020
comment
Отличное объяснение. Тем не менее, я столкнулся с одну ситуацию, которая производит ошибку пока еще запутывается меня. В связанном примере тип C должен быть эквивалентен параметру типа T. Это просто ограничение отображаемых типов (которые Pick используются внутри)? - person MrWolfZ; 09.08.2020
comment
@MrWolfZ В выражении const f = <T extends X>(a: T) => //... термин extends выражает требование, что T ДОЛЖЕН подчиняться любому из следующих двух требований: или (1) T равно X, или; (2) T равно любому произвольному подтипу X. Люди ошибочно предполагают, что термин extends выражает только (1), а не выражает (2). Но поскольку (2) также возможно, TS запрещает вам назначать конкретный экземпляр типа X типу T и ожидать, что вы выберете другой approuch, если хотите оставаться типобезопасным. (Так что в вашем случае это не ограничение для Pick, это ограничение для самого T). - person Flavio Vilante; 11.08.2020
comment
У меня работает const func = <A extends Foo>(a: A = foo_SubType as Foo) - person transang; 09.10.2020
comment
@transang все еще не работает у меня, не могли бы вы прислать рабочий пример? - person Flavio Vilante; 18.10.2020
comment
@FlavioVilante в моем последнем комментарии была ошибка. Рабочий должен быть const func = <A extends Foo>(a: A = foo_SubType as A). Проверьте . Надеюсь на эту помощь! - person transang; 19.10.2020

Эта ошибка предупреждает вас, что ваш общий тип P не может быть назначен {}, поскольку общий тип P может быть более определенным или ограниченным для определенного типа, который может конфликтовать со значением по умолчанию.

Это означает, что значение {} не может удовлетворять всем возможным типам, которые могут использоваться универсальным типом P.

Давайте создадим еще один пример только с логическими значениями, который должен быть более понятным:

interface OnlyBoolIdentityInterface<T> {
  (arg: T): T;
}

function onlyBoolGeneric<T extends boolean>(arg: T = false): T {
  return arg;
}

если вы определяете тип, который является более конкретным, чем логическое значение, например:

type TrueType = true;

и если вы специализировали функцию OnlyBoolIdentityInterface для поддержки только таких истинных значений:

const onlyTrueIdentity: OnlyBoolIdentityInterface<TrueType> = onlyBoolGeneric;

даже если TrueType соблюдает ограничение, установленное T extends boolean, значение по умолчанию arg: T = false не равно TrueType.

Это та ситуация, которую вам пытается донести ошибка.

Итак, как можно исправить этот тип ошибок?

  1. Или вы удалите значение по умолчанию
  2. Или T необходимо расширить специализированный тип параметра по умолчанию, который в моем примере равен false
  3. Или T может напрямую вмешиваться в параметры, которые получают параметры по умолчанию

Для получения дополнительной информации об этом сообщении об ошибке см. Проблему, которая предлагала это сообщение об ошибке https://github.com/Microsoft/TypeScript/issues/29049.

person Fetz    schedule 21.06.2019
comment
Я заменил P extends object = {} на P extends object = any в type Styles, и это решило мою проблему. Спасибо - person teux; 22.06.2019
comment
Я не понимаю ни одного из ответов. Я думаю, что у меня есть механика, которая делает это невозможным, но я не понимаю, почему это должно быть преднамеренное предупреждение. Используя fn из вашего ответа, и fn(true), и fn(false) верны, верно? Разве часть = false в определении функции не дает ей значение по умолчанию, поэтому fn() будет эквивалентно fn(false)? Почему obj, возможно, true повлияет на мое намерение установить параметр по умолчанию false? - person T Tse; 20.03.2020
comment
@ShioT, когда вы создаете универсальную функцию, вы позволяете кому-либо, использующему эту функцию, сделать новую версию более специализированной. Так что, если сделать новую версию, которая принимает только истинные ценности вашего значения по умолчанию будет разорвать эту возможную специализированную версию возможно, этот другой пример кода, который я создал, поможет - person Fetz; 06.05.2020
comment
@Fetz Теперь это имеет смысл. - person T Tse; 07.05.2020
comment
Читая ветку в третий раз, я понял, что до сих пор: чтобы исправить это, вам, вероятно, нужно упростить код или удалить параметры по умолчанию. Согласитесь, это, вероятно, лучше рассматривать как сообщение об ошибке. - person Thom; 09.06.2020
comment
Да, ты прав, Томазелла. Поскольку тип Generic потенциально может конфликтовать с параметрами по умолчанию, лучший вариант, скорее всего, не использовать параметр по умолчанию, поскольку на obj влияет как T, так и параметр по умолчанию. - person Fetz; 11.06.2020

Немного более короткое объяснение.

Пример, который вызывает ошибку:

type ObjectWithPropType<T> = {prop: T};

// Mind return type - T
const createCustomObject = <T extends ObjectWithPropType<any>>(prop: any): T => ({ prop });

type CustomObj = ObjectWithProp<string> & { id: string };

const customObj = createCustomObj<CustomObj>('value'); // Invalid
// function will only ever return {prop: T} type.

Проблема здесь в том, что возвращаемый объект всегда будет соответствовать только атрибуту prop, а не любому другому атрибуту. Расширение ObjectWithPropType дает ложное ощущение ограничения типа. Этот пример в целом является неправильным подходом, он был использован только для иллюстрации, чтобы показать реальный конфликт в атрибутах объекта.

Как ограничить подтип в функции создания:

type StringPropObject = ObjectWithPropType<string>

const createCustomObject = <T>(prop: T extends ObjectWithPropType<infer U> ? U : T): ObjectWithPropType<T> => ({ prop });

const stringObj = createCustomObject<StringPropObject>('test');

В этом случае функция требует, чтобы аргумент был строкой. Объект имеет только атрибут prop, а функция возвращает требуемую форму.

person Mr Br    schedule 06.05.2020