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?
typescript
teux
quelle
quelle
Antworten:
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 Parameter
in 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
A
ist ein Untertyp des TypsS
: WennA
Details hinzugefügt werden, ohne bereits vorhandene Details aus zu entfernenS
.Typ
A
und TypB
sind unterschiedliche Untertypen des TypsS
: IfA
undB
sind Untertypen vonS
, aberA
undB
sind unterschiedliche Typen. Mit anderen Worten:A
undB
fügt dem Typ Details hinzuS
, aber sie fügen nicht das gleiche Detail hinzu .Beispiel: Im folgenden Code sind alle folgenden Aussagen wahr:
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
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 Constraint
ist das 'B'.const func = <A extends B>(a: A) => `hello!`
Liest: Typ Einschränkung 'B' ist die
constraint 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 Constraint
auferlegt 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.
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
A
vom 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 Parameter
ist falsch , da in TS dasType Parameter
kann 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
quelle
hello!
" nicht gleichbedeutend mit "(a: Foo) =>hello!
"?<A extends Foo>(a: A = foo) => 'hello!'
erzeugt einen Kompilierungsfehler, solange(a: Foo = foo) => 'hello'
gültiger Code ist. Siehe hier.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).const f = <T extends X>(a: T) => //...
der Begriffextends
eine Anforderung aus,T
die einer der folgenden zwei Anforderungen entsprechen muss: oder (1)T
ist gleichX
oder; (2)T
ist gleich einem beliebigen Untertyp vonX
. Menschen gehen fälschlicherweise davon aus, dass der Begriffextends
nur (1) und nicht (2) ausdrückt. Da jedoch (2) auch möglich ist, verhindert TS, dass Sie dem Typ eine bestimmte Instanz des Typs zuweisenX
,T
und 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 sichT
selbst.)Dieser Fehler warnt Sie, dass Ihr generischer Typ
P
nicht zugewiesen werden kann{}
, da der generische TypP
definierter 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önnenP
.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 boolean
den Standardwert festgelegte Einschränkung berücksichtigt,arg: T = false
ist dies nicht aTrueType
.Dies ist die Situation, die der Fehler Ihnen zu vermitteln versucht.
Wie können Sie diese Art von Fehlern beheben?
Weitere Informationen zu dieser Fehlermeldung finden Sie in dem Problem, das diese Fehlermeldung vorgeschlagen hat: https://github.com/Microsoft/TypeScript/issues/29049 .
quelle
P extends object = {}
mitP extends object = any
intype Styles
und dies löst mein Problem. Thxfn
aus Ihrer Antwort beidefn(true)
undfn(false)
sind richtig, oder? Gibt der= false
Teil in der Funktionsdefinition nicht einen Standardwert an,fn()
entspricht alsofn(false)
? Warum würde sich diesobj
möglicherweisetrue
auf meine Absicht auswirken, dass der Standardparameter sein sollfalse
?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):
Worin besteht das Problem?
StylesObject
muss ein Subtyp sein (kompatibel mit)Styles
.extends Styles
wir kein generisches Typargument fürStyles
. SoP
wird mit dem instanziert werden Standardtyp.{}
StylesObject<P>
effektiv möchte erweiternStyles<{}>
, aber die beiden sind inkompatibel .const myStylesObject: StylesObject<{ foo: string }> = ...; const styles: Styles<{}> = myStylesObject // error: incompatible
StylesObject
Erlaubt im Prinzip jeden Argumenttyp, der die Einschränkung erweitertobject
(Standard hier= {}
nicht wichtig). UndStyles<{}>
wäre kompatibel mitobject
. Dies ist, was Fehlerteil (a) sagt.Aber was ist, wenn
P
es sich um einen engeren Subtyp handeltobject
, wiemyStylesObject
im obigen Code? Es würde nicht mehr funktionieren. Dies ist, was Fehlerteil (b) sagt.Analogie zwischen Hunden und Tieren
Spielplatz und weitere Infosconst 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>; }
StylesObject
ist der gleiche Typ wie zuvor, indem er sich vonStyles
mit&
/ Kreuzung erstreckt. Vorteil: Sie können jetzt deklarierenStyles<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
StylesFn
ein Objekttyp mit einer Methodendeklaration sein, wie zcreate
. Spielplatz und weitere Infosquelle
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
prop
und keinem anderen Attribut entspricht. Das Erweitern vonObjectWithPropType
gibt 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
prop
Attribute und Funktionen, die die erforderliche Form zurückgeben.quelle