könnte mit einem anderen Subtyp der Einschränkung 'Objekt' instanziiert werden

77

A hat einen Tippfehler bei rekursiven Typen.

Ich versuche, Typen für das Objekt react-jss styles zu schreiben.

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>
};

Es funktioniert gut, aber Typoskript schreibt Fehler. Ich benutze @ ts-ignore, aber das ist nichts Besonderes

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

Was bedeutet dieser Fehler?

teux
quelle
Es ist dieselbe Fehlermeldung wie in dieser Frage, die teilweise durch Kommentare beantwortet werden kann.
ChrisW

Antworten:

135

Ergänzt @fetzz tolle Antwort.


KURZE ANTWORT

TLDR; Es gibt zwei häufige Ursachen für diese Art von Fehlermeldung. Sie machen den ersten (siehe unten). Im Text erkläre ich ausführlich, was diese Fehlermeldung vermitteln soll.

URSACHE 1: In Typscript darf eine konkrete Instanz keinem Typparameter zugewiesen werden. Im Folgenden sehen Sie ein Beispiel für das 'Problem' und das 'Problem gelöst', sodass Sie den Unterschied vergleichen und sehen können, welche Änderungen vorgenommen wurden:

PROBLEM

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

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

LÖSUNG

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

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

Siehe in: TS-Spielplatz

URSACHE 2: Obwohl Sie unten keinen Fehler in Ihrem Code machen. Es ist auch ein normaler Umstand, dass diese Art von Fehlermeldung auftaucht. Sie sollten dies vermeiden:

Wiederholen Sie (aus Versehen) das Type Parameterin einer Klasse, einem Typ oder einer Schnittstelle.

Lassen Sie sich nicht von der Komplexität des folgenden Codes verwirren. Ich möchte nur, dass Sie sich darauf konzentrieren, wie das Entfernen des letzten "A" das Problem löst:

PROBLEM:

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!
})

LÖSUNG:

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
})

Siehe in: TS-Spielplatz


LANGE ANTWORT


DIE FEHLERMELDUNG VERSTEHEN

Im Folgenden werde ich jedes Element der folgenden Fehlermeldung zerlegen:

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'

WAS IST TYP? {}

Es ist ein Typ, dem Sie alles außer null oder undefiniert zuweisen können. Zum Beispiel:

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

Siehe in: TS-Spielplatz


WAS IST is not assignable

Zuweisen bedeutet , dass eine Variable eines bestimmten Typs einer bestimmten Instanz entspricht. Wenn Sie den Typ der Instanz nicht übereinstimmen, wird eine Fehlermeldung angezeigt. Zum Beispiel:

// 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


WAS IST A. different subtype

Zwei Typen sind gleich : Wenn sie keine Details zueinander hinzufügen oder entfernen.

Zwei Typen sind unterschiedlich : wenn sie nicht gleich sind.

Typ Aist ein Untertyp des TypsS : Wenn ADetails hinzugefügt werden, ohne bereits vorhandene Details aus zu entfernen S.

Typ Aund Typ Bsind unterschiedliche Untertypen des TypsS : If Aund Bsind Untertypen von S, aber Aund Bsind unterschiedliche Typen. Mit anderen Worten: Aund Bfügt dem Typ Details hinzu S, aber sie fügen nicht das gleiche Detail hinzu .

Beispiel: Im folgenden Code sind alle folgenden Aussagen wahr:

  1. A und D sind gleiche Typen
  2. B ist der Subtyp von A.
  3. E ist kein Subtyp von A.
  4. B und C sind unterschiedliche Subtypen von 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

HINWEIS : Strukturtyp

Wenn Sie in TS die Verwendung von typeSchlüsselwörtern sehen, type A = { foo: 'Bar' }sollten Sie beispielsweise lesen: Der Typalias Azeigt auf die Typstruktur{ foo: 'Bar' } .

Die allgemeine Syntax lautet : type [type_alias_name] = [type_structure].

Das Typoskript-Typsystem prüft nur gegen [type_structure]und nicht gegen das [type_alias_name]. Das bedeutet, dass es in TS keinen Unterschied in Bezug auf die Typprüfung gibt zwischen: type A = { foo: 'bar }und type B = { foo: 'bar' }. Weitere Informationen finden Sie unter: Official Doc .


WAS IST constraint of type'X'

Die Typbeschränkung ist einfach, was Sie rechts neben dem Schlüsselwort "Erweitert" einfügen. Im folgenden Beispiel Type Constraintist das 'B'.

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

Liest: Typ Einschränkung 'B' ist dieconstraint of type 'A'


WARUM DER FEHLER PASSIERT

Zur Veranschaulichung zeige ich Ihnen drei Fälle. Das einzige, was in jedem Fall variieren wird, ist Type Constraint, dass sich nichts anderes ändern wird.

Ich möchte, dass Sie bemerken, dass die Einschränkung, die Type Constraintauferlegt wird, Type Parameter keine unterschiedlichen Untertypen umfasst . Mal sehen:

Gegeben:

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' }

FALL 1: KEINE EINSCHRÄNKUNG

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

FALL 2: EINIGE EINSCHRÄNKUNGEN

Beachten Sie unten, dass die Einschränkung keine Untertypen betrifft.

SEHR WICHTIG : In Typescript werden Type Constraint verschiedene Untertypen nicht eingeschränkt

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

Fall 3: Eingeschränkter

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 !

Siehe auf dem TS-Spielplatz


FAZIT

Die Funktion unten:

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

Ergibt diese Fehlermeldung:

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)

Da Typescript Avom Funktionsaufruf abgeleitet ist, es jedoch keine Einschränkung in der Sprache gibt, die Sie darauf beschränkt, die Funktion mit verschiedenen Untertypen von 'Foo' aufzurufen. Zum Beispiel werden alle Funktionsaufrufe unten als gültig angesehen:

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

Daher konkreter auf eine generische Zuordnung Type Parameterist falsch , da in TS das Type Parameterkann immer zu einer beliebigen anderen Subtyp instanziert werden:

Lösung:

Weisen Sie niemals einem generischen Typparameter einen konkreten Typ zu, betrachten Sie ihn als read-only! Tun Sie stattdessen Folgendes:

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

Siehe auf dem TS-Spielplatz

Flavio Vilante
quelle
Ist "<A erweitert Foo> (a: A) => hello!" nicht gleichbedeutend mit "(a: Foo) => hello!"?
Trevor Karjanis
@TrevorKarjanis sie sind wenig anders zum Beispiel: <A extends Foo>(a: A = foo) => 'hello!'erzeugt einen Kompilierungsfehler, solange (a: Foo = foo) => 'hello' gültiger Code ist. Siehe hier.
Flavio Vilante
1
@ Max Danke. Sie fragten: Why? SubFoo and DiffSubType both implement same prop1 and both add one additional prop, shouldn't they be both be just subtypes of Foo?Sie sind nur Untertypen von Foo, Sie liegen nicht falsch. Die Frage ist: Sind sie, wenn sie Subtypen sind, gleiche oder unterschiedliche Subtypen? Antwort: Unterschiedlich, weil sie unterschiedliche Eigenschaften / Methoden in Bezug zueinander hinzufügen. Sehen Sie sich dieses Beispiel hier an (vielleicht kennen Sie das Schlüsselwort interface besser).
Flavio Vilante
1
@ Ford04 Danke. Ich habe die Antwort aktualisiert, um Ihre Kommentare wiederzugeben.
Flavio Vilante
1
@MrWolfZ Im Ausdruck drückt const f = <T extends X>(a: T) => //...der Begriff extendseine Anforderung aus, Tdie einer der folgenden zwei Anforderungen entsprechen muss: oder (1) Tist gleich Xoder; (2) Tist gleich einem beliebigen Untertyp vonX . Menschen gehen fälschlicherweise davon aus, dass der Begriff extendsnur (1) und nicht (2) ausdrückt. Da jedoch (2) auch möglich ist, verhindert TS, dass Sie dem Typ eine bestimmte Instanz des Typs zuweisen X, Tund erwartet, dass Sie einen anderen Ansatz wählen, wenn Sie typsicher bleiben möchten. (In Ihrem Fall handelt es sich also nicht um eine Einschränkung von Pick, sondern um eine Einschränkung für sich Tselbst.)
Flavio Vilante
41

Dieser Fehler warnt Sie, dass Ihr generischer Typ Pnicht zugewiesen werden kann {}, da der generische Typ Pdefinierter oder auf einen bestimmten Typ beschränkt sein kann, der mit dem Standardwert in Konflikt stehen kann.

Dies bedeutet, dass der Wert {}nicht alle möglichen Typen erfüllen kann, die vom generischen Typ verwendet werden können P.

Lassen Sie uns ein weiteres Beispiel mit nur Booleschen Werten erstellen, die leichter zu verstehen sein sollten:

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

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

Wenn Sie einen Typ definieren, der spezifischer als ein Boolescher Wert ist, zum Beispiel:

type TrueType = true;

und wenn Sie die Funktion so spezialisiert haben OnlyBoolIdentityInterface, dass nur echte Werte wie diese unterstützt werden:

const onlyTrueIdentity: OnlyBoolIdentityInterface<TrueType> = onlyBoolGeneric;

Auch wenn TrueType die durch T extends booleanden Standardwert festgelegte Einschränkung berücksichtigt, arg: T = falseist dies nicht a TrueType.

Dies ist die Situation, die der Fehler Ihnen zu vermitteln versucht.

Wie können Sie diese Art von Fehlern beheben?

  1. Oder Sie entfernen den Standardwert
  2. Oder T muss den speziellen Typ des Standardparameters erweitern, der in meinem Beispiel falsch ist
  3. Oder T kann direkt mit Parametern interferieren, die Standardparameter erhalten

Weitere Informationen zu dieser Fehlermeldung finden Sie in dem Problem, das diese Fehlermeldung vorgeschlagen hat: https://github.com/Microsoft/TypeScript/issues/29049 .

Fetz
quelle
I'v ersetzt P extends object = {}mit P extends object = anyin type Stylesund dies löst mein Problem. Thx
teux
1
Ich verstehe keine der Antworten. Ich glaube, ich bekomme die Mechanik, die es unmöglich macht, aber ich verstehe nicht, warum dies eine beabsichtigte Warnung wäre. Verwenden Sie fnaus Ihrer Antwort beide fn(true)und fn(false)sind richtig, oder? Gibt der = falseTeil in der Funktionsdefinition nicht einen Standardwert an, fn()entspricht also fn(false)? Warum würde sich dies objmöglicherweise trueauf meine Absicht auswirken, dass der Standardparameter sein soll false?
ShioT
2
@ShioT Wenn Sie eine generische Funktion erstellen, erlauben Sie jemandem, der die Funktion verwendet, eine neue Version davon spezialisierter zu machen. Wenn Sie also eine neuere Version erstellen,
Fetz
@Fetz Das macht jetzt Sinn.
ShioT
Zum dritten Mal Thread lesen, was ich bisher gedacht habe: Um dies zu beheben, müssen Sie wahrscheinlich den Code vereinfachen oder Standardparameter entfernen. Stimmen Sie zu, dass dies wahrscheinlich besser als Fehlermeldung gedacht ist.
Thom
1

Minimales Beispiel

Das Problem wird klarer, wenn eine verkürzte Version denselben Fehler verursacht:

interface StylesObject<P extends object = {}> extends Styles {
//        ^~~~~~~~~~~^ same error as in question
    foo: (props: P) => void;
}

type Styles<P extends object = {}> = {
    foo: (props: P) => void
};

Fehler (siehe Spielplatz für den vollständigen Nachrichtenstapel):

' {}' ist der Einschränkung vom Typ ' P' (a) zuweisbar , ' P' könnte jedoch mit einem anderen Untertyp der Einschränkung ' object' (b) instanziiert werden .


Worin besteht das Problem?

  1. StylesObjectmuss ein Subtyp sein (kompatibel mit) Styles.
  2. Beim Schreiben setzen extends Styleswir kein generisches Typargument für Styles. So Pwird mit dem instanziert werden Standardtyp. {}
  3. StylesObject<P> effektiv möchte erweitern Styles<{}>, aber die beiden sind inkompatibel .
const myStylesObject: StylesObject<{ foo: string }> = ...;
const styles: Styles<{}> = myStylesObject // error: incompatible

StylesObjectErlaubt im Prinzip jeden Argumenttyp, der die Einschränkung erweitert object(Standard hier = {}nicht wichtig). Und Styles<{}>wäre kompatibel mit object. Dies ist, was Fehlerteil (a) sagt.

Aber was ist, wenn Pes sich um einen engeren Subtyp handelt object, wie myStylesObjectim obigen Code? Es würde nicht mehr funktionieren. Dies ist, was Fehlerteil (b) sagt.

Analogie zwischen Hunden und Tieren

Spielplatz und weitere Infos
const playWithDog = (dog: Dog) => { dog.isBarking = true }
const handleAnimal: (animal: Animal) => void = playWithDog 
// error, cannot assign function that wants to deal with dogs (specific animals) 
// to a variable type that describs a callback for all sorts of animals.

function feedAnimal(animalFeeder: (animal: Animal) => void) { }
feedAnimal((dog: Dog) => { dog.isBarking = true })
// Error: Type 'Animal' is not assignable to type 'Dog'.

Lösungen

Option 1: Verwenden Sie den Typalias für StylesObject

type StylesObject<K extends string = any, P extends object = {}> = Styles<K, P> & {
    [x: string]: CSS.Properties<JssValue<P>> | Styles<K, P>;
}

StylesObjectist der gleiche Typ wie zuvor, indem er sich von Stylesmit &/ Kreuzung erstreckt. Vorteil: Sie können jetzt deklarieren Styles<K, P>, was mit der Schnittstelle nicht möglich wäre . Weitere Infos in dieser Antwort .

Ich empfehle diese Variante, da keine weiteren Änderungen erforderlich sind. Schauen Sie sich den Spielplatz an .

Option 2: Verwenden Sie die Methodendeklaration in StylesFn

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

Dies muss StylesFnein Objekttyp mit einer Methodendeklaration sein, wie z create. Spielplatz und weitere Infos

ford04
quelle
0

Eine etwas kürzere Erklärung.

Beispiel, das einen Fehler auslöst:

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.

Das Problem hierbei ist, dass das Rückgabeobjekt immer nur dem Attribut propund keinem anderen Attribut entspricht. Das Erweitern von ObjectWithPropTypegibt einen falschen Eindruck von Typbeschränkung. Dieses Beispiel ist insgesamt ein falscher Ansatz, der nur zur Veranschaulichung verwendet wurde, um den tatsächlichen Konflikt in Objektattributen darzustellen.

So beschränken Sie den Subtyp in der Erstellungsfunktion:

type StringPropObject = ObjectWithPropType<string>

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

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

In diesem Fall erfordert die Funktion, dass das Argument eine Zeichenfolge ist. Das Objekt hat nur propAttribute und Funktionen, die die erforderliche Form zurückgeben.

Herr Br
quelle