Definitelytyped: React.d.ts ReadOnly<t>in Zustand und Requisiten</t>

Erstellt am 25. Jan. 2017  ·  91Kommentare  ·  Quelle: DefinitelyTyped/DefinitelyTyped

Hallo @ericanderson

Ich habe viele Probleme mit dieser Änderung, wenn sie in der Praxis verwendet wird:

Problem 1: Gehen Sie zur Definition

Wenn Sie auf einer Eigenschaft von Requisiten oder Status auf Gehe zu Definition klicken, kann Typescript es nicht auflösen.

interface MyComponentProps {
    name: string;
}

export abstract class MyComponent extends React.Component<MyComponentProps , void> {
    myMethood() {
       this.props.name; //<-- Go To definition in name
   }
}

image

Macht Sinn, weil das Mitglied synthetisch generiert wird, nervt aber trotzdem.

Problem 2: Hierarchien von Komponenten (Generic P mit Constraints)

Noch wichtiger, wenn Sie eine abstrakte Komponente wie diese erstellen:

interface MyBaseProps {
    onChange?: (val: any) => void;
}

export abstract class MyBase<P extends MyBaseProps> extends React.Component<P, void> {
    myMethood() {
        this.props.onChange!(2); //The type is S["onChange"] instead of (val: any) => void and so is not invocable. 
   }
}

TS kann zeigen, dass es eine Eigenschaft onChange gibt, kann aber manchmal seinen Typ nicht erkennen.

image

Dies ist die wichtigste Änderung, da sie mich daran hindert, Hierarchien von Komponenten zu haben, die gemeinsame Requisiten und Funktionen teilen. Es sieht aus wie ein Problem im TS-Compiler, aber bis es behoben ist.

Problem 3: Nicht so Readonly.

Obwohl ich zustimme, dass diese Änderung die funktionale Absicht von React gut erfasst, gibt es gültige Situationen, in denen Sie den Zustand zwingend ändern können, wie im Konstruktor, und auch wenn Sie den Zustand ändern und forceUpdate aufrufen, funktioniert alles OK.

C# this.state.name = "John"; this.forceUpdate(); //Ok as long as you don't setState afterwards, but calling setState also is annoying with the callback.

Ist es empfehlenswert? Nein.
Ist es verboten? Auch nicht, sonst existiert forceUpdate nicht.

Natürlich könnten Sie den Status in S (oder any ) umwandeln und die Änderung vornehmen, aber wenn es sich um ein allgemeines Muster handelt, wird es umständlich.

Fazit: Lohnt es sich?

Ich bin traurig, dass das neue glänzende Feature von TS in diesem Fall mehr Probleme als Lösungen macht, aber ich denke ehrlich, dass dies hier der Fall ist.

Auf der anderen Seite ist die Änderung von setState großartig 👍 , wusste nichts über Pick<S,K> .

Hilfreichster Kommentar

Problem 3 steht zur Debatte, denke ich.

Sie haben Recht, dass Sie das obige Beispiel _technisch_ in React ausführen können, aber ich würde definitiv argumentieren, dass dies nicht die Art und Weise ist, wie React verwendet werden sollte.

Dies kann in 3 verschiedene Fälle unterteilt werden.

Generische Initialisierung

interface State {
  bar: number;
}

interface Props {
  baz: number;
}

class Foo extends React.Component<Props, State> {
  public state: State = {
    bar: 5,
  };
}

Initialisierung basierend auf Requisiten

interface State {
  bar: number;
}

interface Props {
  baz: number;
}

class Foo extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      bar: props.baz,
    };

    // or
    this.setState({
      bar: props.baz,
    });
  }
}

Zufällige Zuweisung mit forceUpdate

Angesichts der Tatsache, dass es meiner Meinung nach besser ist, die Leute zum "Richtigen" zu drängen, können Sie dieses Problem leicht umgehen, indem Sie public state neu deklarieren:

interface State {
  bar: number;
}

class Foo extends React.Component<{}, State> {
  public state: State;
  public myMethod() {
    this.state.bar = 5;
  }
}

Alle 91 Kommentare

Welche Typoskript-Version verwendet Ihr visuelles Studio?

@vsaio für sa

Für Problem 1 mit TS 2.1.5 und dem neuesten VSCode funktioniert dies für mich einwandfrei. Ich habe kein Windows/VS, also kann ich dort nicht nachsehen, aber ich würde wetten, dass es Updates für Ihre Plugins gibt oder Sie nicht auf TS 2.1.5 sind

Dasselbe gilt für Aufgabe 2

GEGENÜBER 2015 mit TS 2.1.5.0

Problem 3 steht zur Debatte, denke ich.

Sie haben Recht, dass Sie das obige Beispiel _technisch_ in React ausführen können, aber ich würde definitiv argumentieren, dass dies nicht die Art und Weise ist, wie React verwendet werden sollte.

Dies kann in 3 verschiedene Fälle unterteilt werden.

Generische Initialisierung

interface State {
  bar: number;
}

interface Props {
  baz: number;
}

class Foo extends React.Component<Props, State> {
  public state: State = {
    bar: 5,
  };
}

Initialisierung basierend auf Requisiten

interface State {
  bar: number;
}

interface Props {
  baz: number;
}

class Foo extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      bar: props.baz,
    };

    // or
    this.setState({
      bar: props.baz,
    });
  }
}

Zufällige Zuweisung mit forceUpdate

Angesichts der Tatsache, dass es meiner Meinung nach besser ist, die Leute zum "Richtigen" zu drängen, können Sie dieses Problem leicht umgehen, indem Sie public state neu deklarieren:

interface State {
  bar: number;
}

class Foo extends React.Component<{}, State> {
  public state: State;
  public myMethod() {
    this.state.bar = 5;
  }
}

Meine Probleme sind mit der Generika-Varianz. speziell für die Eingabe innerhalb der Klasse, die generisch typisiert ist. Unten ist ein ziemlich minimales Beispiel dafür, wo die Dinge kaputt gehen.

class TBaseState {
  public value: string;
}

function globalFunc<T extends Readonly<TBaseState>>(item: T) {
}

class MyComponent<TProps, TState extends TBaseState> extends React.Component<TProps, TState> {
  broken() {
    // typing of this.state is Readonly<TState>
    // this is not assignable to Readonly<TBase>
    globalFunc(this.state);

    // this is a horrible hack to fix the generics variance issue
    globalFunc(this.state as TState as Readonly<TBaseState>);
  }
}

class MyState extends TBaseState {
}

let component: MyComponent<any, MyState>;

// here the typing of component.state is Readonly<MyState>
// this is assignable to Readonly<TBase>
globalFunc(component.state);

Ich bin in TS 2.1.5.0

image

aber könnte sein, dass wir in VS eine schlechtere TS-Erfahrung haben als in VS-Code ...

Gehen Sie für Problem 1 zur Definition TS funktioniert auch nicht in VS Code:

interface MyComponentProps {
    name: string;
}

export abstract class MyComponent extends React.Component<MyComponentProps , void> {
    fullName: string;
    myMethood() {
       this.props.name; //<-- doesnt work
       this.fullName; //<-- works
   }
}

für Problem 2 ist es wahr, dass VS Code sich besser verhält:

image

während VS verwirrt aussieht:

image

Ich denke, für VSCode und Problem 1 funktioniert es, weil ich das Plugin für die "Neueste Typoskript- und Javascript-Grammatik" verwende, die eine intelligentere Handhabung haben muss.

@patsissons das ist ein interessantes Beispiel, obwohl ich denke, dass es repräsentativer für einen Fehler in Typoskript ist als für einen Fehler in der Definitionsdatei. Zum Beispiel nahm setState früher S , was bedeutete, Teiltöne zu machen, wir mussten komische Tricks wie setState({foo:5} as any as State) machen oder den verwenden, der eine Funktion übernimmt. Ich bin mir nicht sicher, ob der Mangel an Expressivität des Compilers die Eingaben "falsch" macht. Ich denke, dies ist ein gutes Argument für eine Änderung der README-Datei, um diesen Grenzfall zu markieren.

Hast du ein Problem bei TS gemeldet?

Diese Änderung unterbricht heutzutage also alle VS und deaktiviert Go To Definition in allen VS-Codes, außer wenn Sie ein Plug-In haben ...

Außerdem gibt es das Vollständigkeitsargument. Es gibt Millionen von APIs, die schreibgeschützt sein sollen und heutzutage nicht mehr vorhanden sind, nur in React.d.ts

 interface ComponentLifecycle<P, S> {
        componentWillMount?(): void;
        componentDidMount?(): void;
        componentWillReceiveProps?(nextProps: Readonly<P>, nextContext: any): void;
        shouldComponentUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: Readonly<any>): boolean;
        componentWillUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: Readonly<any>): void;
        componentDidUpdate?(prevProps: Readonly<P>, prevState: Readonly<S>, prevContext: Readonly<any>): void;
        componentWillUnmount?(): void;
    }

Ich denke, readonly sollte für „Freeze“ oder „Inmmutable.js“ verwendet werden, nicht für den langen Schwanz von Gedanken, die nicht geändert werden sollen, wie zum Beispiel Ereignisobjekte.

Ich habe nicht eingereicht, ich habe heute nur meinen Code nachgerüstet, um mit den neuen Readonly<T> -Typen umzugehen. Dies war ein Fall, auf den ich gestoßen bin, für den ich keine richtig typisierte Lösung hatte. Machen Sie weiter und reichen Sie ein Problem ein. Ich werde heute den größten Teil des restlichen Tages mit dem Patchen von Code beschäftigt sein.

Ah ja, ich wusste, dass ich einige verpasst habe. @olmobrutall Wenn wir die von mir eingeführten Zustandsänderungen beibehalten, um sie als schreibgeschützt zu markieren, stimme ich zu, dass diese Methoden aktualisiert werden sollten. Ich habe jedoch das Gefühl, dass wir zuerst einen Konsens darüber brauchen, was richtig zu tun ist.

Was die VS-Brüche betrifft, weiß ich nicht, was richtig ist. Sollten die Typen zurückgehalten werden, weil einige Tools nicht auf dem neuesten Stand bleiben?

@patsissons Sie können vorerst immer Ihre eigenen Eingaben für die Reaktion bereitstellen, wenn Sie abwarten möchten, wie sich dies entwickelt, bevor Sie Ihren gesamten Code aktualisieren. https://ericlanderson.com/using-custom-typescript-definitions-with-ts-2-x-3121db84015d#.ftlkojwnb

Unserer Erfahrung nach hinkt VS immer etwas hinterher. Unser Shop verwendet vscode für jede aktive Typoskript-Entwicklung und VS wird eher zum einfachen Patchen von Codedateien oder für Nicht-Typoskript-Entwickler zum Durchsuchen von Code verwendet, nicht unbedingt für die aktive Entwicklung.

@ericanderson der hack ist erstmal nicht so schlimm, ich muss nur Readonly<T> aussieben um mein T zu bekommen welches den Readonly<Base> zuweisbar ist.

Wir sprechen von 'react.d.ts', diese Deklaration eines einzigen Mitglieds wird massiv verwendet. Ich denke, es lohnt sich, sich zurückzuhalten, bis VS fertig ist.

Außerdem sind 50 % der Typen auf der Welt dazu bestimmt, schreibgeschützt zu sein, wie Objekte, die Sie von APIs erhalten. Ich glaube nicht, dass wir das kommentieren müssen.

Ich denke, Readonly sollte für Objekte verwendet werden, die explizit so konvertiert wurden, dass sie Nur-Get-Eigenschaften haben. Wie Einfrieren.

@olmobrutall Readonly ist neu, daher ist die genaue Best Practice nicht wirklich definiert. Ich persönlich würde es vorziehen, wenn alles erklärt, dass es Readonly<> an Dingen braucht, um zu signalisieren, dass es nicht mutieren wird. In ähnlicher Weise erwartet React nicht, dass Sie state außerhalb von setState ändern, und somit stellt diese Änderung sicher, dass Unfälle keine Fehler einführen, was einer der Hauptvorteile der Verwendung von TypeScript gegenüber Javascript ist.

Wenn die Leistung in allen Browsern für Object.freeze konsistenter wäre, würde ich mir vorstellen, dass die React-Leute tatsächlich nach setState anfangen würden einzufrieren.

Was ist dann der Zweck von forceUpdate?

Ich bin neugierig auf die Gedanken anderer Leute darüber, wie DefinitelyTyped in Bezug auf Tooling-Updates funktionieren sollte, sowie auf die Philosophie von Readonly on React (und andere Bibliotheken, deren Absicht es ist, dass Sie bestimmte Objekte nicht ändern).

cc/ @johnnyreilly @vsaio @pspeter3 für Gedanken zur Reaktion im Besonderen und andere Gedanken im Allgemeinen
cc/ @andy-ms @mhegazy für Gedanken darüber, wie DefinitelyTyped philosophisch für Werkzeugaktualisierungen und den eifrigen Einsatz von Readonly vorgehen sollte

@olmobrutall Wir verwenden forceUpdate , um ein Rendering auf der Reaktionsseite in die Warteschlange zu stellen, angetrieben von beobachtbaren Ereignissen auf der Statusseite.

AKTUALISIEREN :
Ich werde unser Szenario ein wenig erläutern, damit es nicht missverstanden wird. Unsere Zustandsobjekte sind langlebige, unveränderliche Objekte (daher ist Readonly<T> eigentlich sehr gut für uns geeignet). Diese Zustandsobjekte enthalten mehrere beobachtbare rxjs -Streams, die in ein beobachtbares Benachrichtigungsobjekt namens stateChanged . React-Komponenten beobachten dieses Observable auf Ereignisse und leiten diese Ereignisse in einen Aufruf an forceUpdate (nach dem Entprellen). Tatsächlich lebt unser veränderlicher Staat innerhalb des Staates, aber der Staat selbst und die Mitglieder, die im Staat existieren, sind alle unveränderlich. Dies ist sicherlich nicht der Standardanwendungsfall für React, aber es ist ein sehr ähnlicher Ablauf. Wir haben einfach intelligente Zustandsobjekte, die wissen, wie sie die Komponenten informieren, wenn ein erneutes Rendern erforderlich ist.

@ericanderson das Hauptproblem ist, dass diese Typdefinitionen unter SemVer-Problemen leiden. Da die Typdefinitionsversionen weitgehend an ihre jeweiligen Modulversionen gebunden sind, endet dies mit einem kleinen Versionsstoß, der bahnbrechende Änderungen der Typdefinition mit sich bringt, was bedeutet, dass wir @types -Versionen in unserem package.json müssen Datei.

@olmobrutall Aus den Dokumenten von React:

Normalerweise sollten Sie versuchen, alle Verwendungen von forceUpdate() zu vermeiden und nur von this.props und this.state in render() zu lesen.

Der Reaktionsleitfaden sagt Ihnen sogar, dass Sie den Status nicht direkt aktualisieren sollen: https://facebook.github.io/react/docs/state-and-lifecycle.html#do -not-modify-state-directly

forceUpdate dient, wie ich es gelesen habe, nur dazu, Ihre Komponente zur Aktualisierung zu zwingen, wenn Ihre Komponente auf Daten _nicht_ in Ihren Requisiten oder Ihrem Status basiert.

@patsissons Ich kann mich irren, aber ich glaube, dass SemVer so konzipiert ist, dass es abwärtskompatibel mit APIs und semantischen Absichten ist. Nur weil Sie eine Bibliothek auf eine Weise verwenden, die nicht beabsichtigt war (gemäß der Dokumentation), bedeutet dies nicht, dass die Bibliothek diese unbeabsichtigten Verwendungen weiterhin unterstützen muss. Bibliotheksautoren sind gut in SemVer, um Semantiken zu ändern, die falsch waren, aber zufällig von einigen Leuten verwendet wurden.

Das heißt, vielleicht ist Readonly<> zu state eine zu große Änderung, aber nehmen Sie für einen Moment an, dass es die richtige Änderung ist. Wann sollte es in DefinitelyTyped veröffentlicht werden? Ihr Code muss immer geändert werden, sobald Sie das Update erhalten, das schließlich state als Readonly<> markiert.

Ich weiß immer noch nicht, was daran richtig ist, Readonly<> auf state anzuwenden, was es schwierig macht, Semver oder Werkzeuge oder irgendetwas anderes zu diskutieren. Mein Gefühl war, dass es richtig war. Die Leute, die die Änderung überprüft haben, haben sie nie als Problem angesprochen. Es scheint mit der Absicht des React-Teams übereinzustimmen.

Ich verweise gerne auf einen der Rezensenten für eine Reaktion in DefinitelyTyped (ich habe sie alle oben auf CC gesetzt).

Das Observable ändert also den Status und Sie erzwingen ein Update? Das Ändern des Status ist also zwingend zulässig.

Ich denke, wo Readonly verwendet werden soll, ist nicht zu 100% definiert. Aber müssen wir mit einer umstrittenen und massiv genutzten Eigenschaft beginnen, bevor die Werkzeuge fertig sind?

Ich bin ganz für stark typisiert, ich verwalte 6 Projekte alle mit strictNullChecks, aber die Vorteile hier sind viel kleiner als die Probleme, die es derzeit produziert.

@ericanderson Ich glaube, SemVer2 ist so konzipiert, dass es Knotenversionsdeklarationen wie ^15.0.0 zulässt und erwartet, dass alle kleineren oder Patch-Upgrades (dh 15.0.1 oder 15.1.0 ) für das Modul transparent sind oder zumindest abwärtskompatibel aus externer Sicht. Alle größeren Upgrades ( 16.0.0 ) würden eine Anpassung der Versionserklärung erfordern, um die Änderung einzuführen. Dies verhindert im Wesentlichen, dass Breaking Changes in das Schreibsystem gebracht werden. Aber derzeitige Typdefinitionsversionen können nicht von der Hauptversion ihrer jeweiligen Modulversion (konventionell) abweichen, was zu dieser Diskontinuität führt.

Kurz gesagt können Typdefinitionen Breaking Changes enthalten, ohne dass sich das Modul selbst überhaupt ändert, und Breaking Changes erfordern einen Major Version Bump.

Aber Sie werden keine PR machen, um forceUpdate zu entfernen, oder?

Dann, wenn forceUpdate vorhanden ist, sollte auch das Ändern des Zustands unbedingt vorhanden sein.

Theoretisch sollten Sie einen tiefen unveränderlichen Zustandsgraphen verwenden, aber sehr oft ist es in Ordnung, den Zustand zwingend zu ändern, und nicht alle Entwickler glauben an die Idee der persistenten Datenstrukturen.

Glücklicherweise erlaubt React einen Scape-Weg und macht direkte Änderungen des Zustands möglich, werden wir TS-Entwicklern verbieten, diesen Weg zu gehen? Ist das nicht zu paternalistisch?

Vue.js zum Beispiel fördert die imperativen Änderungen, ich bin nicht überrascht, wenn es React beeinflusst.

Außerdem habe ich vor nicht allzu langer Zeit (ich kann mich erinnern) einen Blogbeitrag eines React-Autors gelesen, in dem er ermutigt wurde, React ohne die ganze Redux-Zeremonie zu verwenden.

Ansonsten ist meine Position, Typendefinitionen so schnell wie möglich zu veröffentlichen, meine einzige Sorge ist die Automatisierung. da angesichts des aktuellen Stands der Typdefinitionsversionierung ein nachfolgender Build ohne Quelländerungen brechen kann. Diese Arten von nicht deterministischen CI-Ausfällen sind besorgniserregend. Niemand möchte sehen, dass sein Build kaputt geht, weil er eine Änderung durchgeführt hat, und dann herausfindet, dass seine Änderungshand nichts mit dem Build-Breaking zu tun hat.

Nachdem ich darüber geschlafen habe, ist meine persönliche Meinung wie folgt:

  • Eine gute Praxis wäre die Verwendung von Garn- oder NPM-Sperren, sodass Sie keine Überraschung erleben würden, wenn Sie nicht zuerst lokal aktualisiert haben.
  • Den Zustand schreibgeschützt zu machen, ist die Art und Weise, wie React verwendet werden soll. Die Dokumentation und Beispiele bestätigen dies.
  • Der Workflow, in dem Sie setState nicht verwenden möchten, befindet sich in Ihrer Codebasis und innerhalb Ihrer eigenen Rechte. Sie haben Recht, dass React forceUpdate bereitstellt, aber seine Verwendung soll ein Rendering verursachen, wenn Sie sich außerhalb des beabsichtigten Anwendungsfalls befinden. Wenn Sie also state nicht wie beabsichtigt verwenden möchten, ist das in Ordnung, aber an diesem Punkt müssen Sie die Instanzvariable state nicht verwenden. Tatsächlich können Sie einfach normale private Variablen verwenden.
  • Ja, viele Leute verlassen sich auf dieses Projekt, und doch sind dies bisher die einzigen zwei Beschwerden, die mich glauben lassen, dass dies kein weit verbreitetes Problem ist. Außerdem kann das Problem, das bezüglich der globalen Funktion aufgeworfen wurde, einfach umgeschrieben werden, um das Generische anders zu nehmen (siehe verlinktes TypeScript-Problem).

Angesichts der obigen Gedanken sowie der Problemumgehungen für nicht standardmäßige React-Apps denke ich, dass Readonly korrekt ist und die einzige erforderliche Änderung der Vollständigkeit halber darin besteht, die Lebenszyklusmethoden entsprechend zu aktualisieren.

Ich stimme zu, dass diese Änderungen absolut sinnvoll sind, mein Anwendungsfall, der Probleme verursachte, war ein sehr einzigartiger Eckfall, dem man selten begegnen sollte. Durch die Anpassung meiner Codebasis an schreibgeschützte Requisiten und Zustände habe ich einige ansonsten nicht erkennbare Tippprobleme festgestellt. Es besteht kein Zweifel, dass die Veröffentlichung der Änderungen die richtige Entscheidung war.

Patsisson, mit VS, VS Code oder einem anderen Editor?

Mein Punkt ist, dass React zwar einen funktionalen Ansatz fördert, aber einen Videospiel-ähnlichen Workflow ermöglicht, bei dem Sie Änderungen an der Welt zwingend vornehmen (state) und dann rendern (forceUpdate). Dieser Ansatz ist jetzt in TS verboten. Eine Art Funktionalismus :)

Ich werde mir dann Alternativen überlegen, um mein aktuelles Ökosystem funktionsfähig zu machen ...

Problem 2 wirft Fehler nur mit strictNullChecks .

[TS] Cannot invoke an expression whose type lacks a call signature. Type '((val: any) => void) | undefined' has no compatible call signatures.

+1 Ich verwende strictNullChecks

@Ericanderson ?

Wie oben erwähnt, hängt dies mit den Werkzeugen zusammen, die eindeutig außerhalb des Anwendungsbereichs von DT liegen. Wenn Sie VSCode verwenden und die Vorschau des kommenden TS-Parsers installieren, habe ich dieses Problem mit strictNullChecks nicht gesehen, als ich eines der oben genannten Dinge geschrieben habe.

Ich habe kein Windows und kann daher nicht mit VS sprechen.

dieser Pick -Patch hat Vorschläge unter VSCode gebrochen. Wenn Sie this.setState({ | }) (Strg + Leertaste) ausführen, wird nichts angezeigt, obwohl der Status klar definiert ist und Partial<State> verwendet wird, da setState den Status der Mitglieder selektiv festlegen kann

IMHO sollte der korrekte Code setState(state: Partial<S>, callback?: () => any): void; lauten

Wie im ursprünglichen Pull-Request-Zweig besprochen, haben wir mit Partial begonnen. Wenn Ihr Statusobjekt jedoch Folgendes ist:

Schnittstellenzustand {
foo: Zeichenkette;
}

Dann könnten Sie mit teilweise Folgendes tun:

setState({foo: undefiniert});

Was eindeutig falsch ist.

Tut mir leid - aber nochmal zu Readonly

React ist nicht nur Redux und setState. React ist auch Mobx und andere beobachtbare Muster, bei denen die Zuweisung von Zustandseigenschaften das HAUPTMERKMAL ist. Die besprochene Änderung beendet die Mobx-Nutzung mit Typoskript vollständig.

Warum also das Verhalten, das im ursprünglichen Reaktionscode nicht vorhanden ist, zur .d.ts-Datei hinzufügen? .d.ts soll aus Sicht des Autors die ursprüngliche Bibliothek widerspiegeln, aber nicht die Leute im richtigen Codierungsstil unterrichten!

@lezious , ich fürchte, ich verstehe deine Position nicht. Können Sie ein Codebeispiel bereitstellen und was ist an den Eingaben in Bezug auf das Beispiel defekt? Danke!

Kein Problem:

das ist meine Staatsklasse

class UserInfoBlockState  
{
    <strong i="7">@observable</strong>                  <- this is mobx way to declare state
    public updating: boolean;
    <strong i="8">@observable</strong> 
    public deleted: boolean;
}

und das ist meine Komponente

<strong i="12">@observer</strong>       <-- this is mobx way to make component react to state change
export class UserPanel extends React.Component<IUserInfoBlockProps, UserInfoBlockState>
{
   ......
     private updateUser()
    {
        this.state.updating = true;
        UsersAPI.update(this.props.user)
       .then(() =>
            {
                this.state.updating = false;      <--- this is the mobx way to work with the state
            }
        ).catch(() =>
            {
                this.showErrror("Server error");
                this.state.updating = false;
            });
    }
   ....

}

und jetzt haben wir (unser Unternehmen mit einem riesigen Projekt, das auf React + Mobx geschrieben wurde) die DT und React zu Beginn des neuen Release-Kreises aktualisiert und ... 3000+ Kompilierungsfehler "Eigenschaft ist schreibgeschützt". Beeindruckend. Was schlagen Sie mir vor - das gesamte Projekt in Redux umzuschreiben, React.d.ts niemals zu aktualisieren oder die Fork-Version immer beizubehalten und zu unterstützen?

@mweststrate , bitte überprüfen Sie dies.

@Iezious Ich schätze deine Position und bitte dich, dich zu beruhigen. Ich versuche, mit Ihnen zu arbeiten. Das hat nichts mit Redux zu tun, sondern pures React.

Ich möchte nicht, dass DT einen Anwendungsfall blockiert, der zuvor funktioniert hat, aber ich denke nicht, dass Ihre Beschreibung der Verwendung von Mobx mit React mit der React-Dokumentation übereinstimmt (noch nicht einmal mit der Mobx-Dokumentation, die ich jetzt gelesen habe es).

React gibt in der Dokumentation deutlich an, dass es 3 Möglichkeiten gibt, den Zustand korrekt zu verwenden, und die allererste ist "Status nicht direkt ändern".

Dies lässt mich glauben, dass die Art und Weise, wie Ihre Codebasis derzeit funktioniert, sehr wahrscheinlich in zukünftigen Versionen von React brechen wird. Beim Durchsuchen von https://github.com/mobxjs/mobx-react sehe ich keinen Vorschlag, dass Sie state auf diese Weise verwenden. Tatsächlich scheinen sie zu wollen, dass Sie Eigenschaften verwenden.

Beim Überprüfen von https://mobx.js.org/getting-started.html und beim Googeln nach "Mobx React State" kann ich keine Dokumentation finden, die darauf hindeutet, dass Sie Mobx so verwenden, wie Sie es tun.

DT soll bestenfalls den Geist der zugrunde liegenden Bibliothek und schlimmstenfalls die tatsächliche Implementierung vermitteln, und es ist klar, dass der Kauf von Reaktions- und Erweiterungskomponenten die Einhaltung des impliziten Vertrags bedeutet.

Ich bin mir nicht sicher, was ich dir vorschlage. Ein paar Möglichkeiten, die mir spontan einfallen:

  1. Eine "günstige" Option, wenn Sie darauf bestehen, die Variable state zu übernehmen, besteht darin, React.Component zu suchen und durch $#$ MyComponent $#$ zu ersetzen und MyComponent als Unterklasse zu definieren von React.Component ohne die schreibgeschützten Einschränkungen.
  2. Eine andere, basierend auf den idiomatischen Beispielen, die in der Dokumentation für Mobx gepostet wurden, ist, sich die Zeit zu nehmen, die Verwendung this.state einzustellen und nur Variablen für die eigentlichen React.Component zu verwenden. Dies mag ein wenig schmerzhaft sein, aber zumindest neue Leute in Ihrem Projekt werden in der Lage sein, die Muster in Ihrer Codebasis so zu sehen, wie sie online beschrieben werden.
  3. Sie könnten state in jeder Komponente neu deklarieren, wenn Sie es ähnlich wie bisher machen möchten.
  4. Sie könnten this.state suchen und durch $# this.somethingElse ersetzen und manuell deklarieren.
  5. Sie könnten aufhören, Aktualisierungen zu übernehmen, um von DT zu reagieren (und möglicherweise in Zukunft von React im Allgemeinen, je nachdem, wie sich zukünftige Änderungen auf Ihren Anwendungsfall auswirken könnten).

Wenn es mein Projekt wäre, würde ich wahrscheinlich Nummer 2 machen, obwohl ich nicht genug über Mobx weiß, um es sicher zu wissen.

Es tut mir leid, dass ich Ihnen nicht zustimmen konnte (was nicht bedeutet, dass andere Ihnen nicht zustimmen werden). Ich habe versucht, einen Grund zu finden, diesen Teil rückgängig zu machen, aber ich kann momentan nicht an Bord kommen.

Ich werde noch einmal unsere Strategie erwähnen, bei der es sich um eine benutzerdefinierte Anwendung von RxJs-Observables handelt, um React-Zustandsänderungen und -Rendering voranzutreiben, die dem Mobx-Muster sehr ähnlich sind. Wir verwenden Aktionen, um Eingaben von der Ansichtsebene (Reaktion) zu erhalten. Aktionen sind gleichbedeutend mit einer Funktion, die Eingaben verbraucht und eine Observable erzeugt, die dann andere Zustandsobservable weiter antreibt. Dieses Muster ermöglicht es, dass der Zustand aus der Perspektive der Reaktionsschicht unveränderlich bleibt, da Sie immer nur beobachtbare Werte abfragen und Zustandsaktionen ausführen. Intern kann sich Ihr Status als Ergebnis der Aktionen selbst "mutieren", da der interne Status keine schreibgeschützten Einschränkungen hat.

Ich kann mich nicht beruhigen. Ich habe das Projekt in der Produktion und muss viel Zeit für Teams aufwenden, und das bedeutet seit dieser Änderung Geld.

Und was ist der Grund? Spiegelt diese Änderung die Realität wider? Nein. Er fügt die Beschränkungen und Verhaltensweisen hinzu, die nicht vorhanden sind, die Beschränkung, die irgendwie hinzugefügt wird, nur weil er denkt, dass dies richtig ist.

Was ist das Ziel des DT-Projekts? JS-Bibliotheken so genau wie möglich zu beschreiben oder unsere Vision von der richtigen Nutzung dieser Bibliotheken zu beschreiben? Dem Namen nach ist es "Definitely Typed" an erster Stelle. Wenn diese Einschränkung also nicht in der ursprünglichen JS-Bibliothek vorhanden ist, DARF sie auch NICHT in DT vorhanden sein. Das ist mein Punkt. Wo liege ich in diesem Punkt falsch?

Ich verstehe, dass Sie frustriert sind, aber das ist die Art und Weise, wie die Reaktion beim Lesen der Dokumente verwendet werden sollte. Ich sehe kaum ein, wie wir DT weniger spezifisch machen sollten, weil Ihr Team die Bibliothek missbraucht und den impliziten Vertrag verletzt hat.

Zeigen Sie mir eine Unze der vom Reaktionsteam herausgegebenen Dokumentation, die darauf hindeutet, dass der Zustand direkt veränderbar sein sollte, und ich werde den Code sofort wieder ändern.

https://facebook.github.io/react/docs/react-component.html#state

Niemals this.state direkt mutieren, da ein nachfolgender Aufruf von setState() die von Ihnen vorgenommene Mutation ersetzen kann. Behandeln Sie this.state so, als wäre es unveränderlich.

Es scheint ziemlich klar zu sein, dass die Reaktion this.state als unveränderlich betrachtet. React betrachtet die _Eigenschaften_ von this.state nicht als unveränderlich (was die Redux-Annahme ist). Sie können Folgendes tun:

this.state.user.name = "foo";

in idiomatischer Reaktion.

Ich bevorzuge es, die API genau einzugeben (was in diesem Fall Readonly bedeutet) und alle Invarianten auszudrücken, die das Reaktionsteam angibt.

@ericanderson Entschuldigung, das ist mir gerade erst aufgefallen. FWIW Ich denke, dass die Änderung angemessen ist und dass die Werkzeuge aufholen werden. Haben Sie nebenbei gehört, dass sie erwägen, die setState -Überladung, die ein Objekt akzeptiert, abzulehnen? Die Zukunft ist auf jeden Fall der reduzierte Stil setState .

@amoreland Stimme nicht zu . Per: https://facebook.github.io/react/docs/state-and-lifecycle.html#do -not-modify-state-directly

Status nicht direkt ändern

Dadurch wird beispielsweise eine Komponente nicht erneut gerendert:

// Wrong
this.state.comment = 'Hello';

Verwenden Sie stattdessen setState():

// Correct
this.setState({comment: 'Hello'});

Der einzige Ort, an dem Sie this.state zuweisen können, ist der Konstruktor.

@johnnyreilly hatte ich nicht. Das ist interessant. Quelle?

Es wurde in einem der Vorträge auf der letzten React-Konferenz behandelt. Es ist auf YouTube verfügbar. Es könnte der Vortrag von Lin Clark gewesen sein. Einer der ersten Vorträge an Tag 1 - über die bevorstehenden Änderungen, um für v16 zu reagieren. Faser usw

Sorry @gaearon meinte ich

Der Grund, warum Mobx Zustandsänderungen durchführt, liegt darin, dass die Observables React-Updates vorantreiben, anstatt den Zustand vollständig zu _ersetzen_, wird der Zustand zu einer Engine, die das Rendern antreibt. Wenn Sie also etwas wie this.state.updating = true; tun, tun Sie tatsächlich das Äquivalent einer Zustandsersetzung, aber stattdessen ist der Zustand intelligent genug, um ein neues Rendern auszulösen, indem er die Komponente benachrichtigt, dass der Zustand gegenüber seinem vorherigen Inhalt geändert wurde. Ich stimme zu, dass dies keine _konventionelle_ Verwendung von React ist, sondern eher eine viel umfassendere und höherwertige Verwendung von React. Ich würde argumentieren, dass die herkömmliche React-Nutzung nur für kleine Projekte mit einer Handvoll Komponenten sinnvoll ist. Wenn Sie am Ende Hunderte von Komponenten mit jeweils mehreren Dutzend reaktiven Mutationstreibern haben, können Sie herkömmliche React-Zustandsänderungen (z. B. setState) nicht mehr zuverlässig verwenden und müssen architektonische Änderungen vornehmen, die den _smart_-Zustand beinhalten (was Mobx im Wesentlichen tut). ).

Das heißt, ich verstehe, warum er verärgert ist, weil die Typänderungen eine fortgeschrittenere Verwendung des React-Systems beeinflussen. Die beobachtbaren Zustandszuweisungen sind technisch gesehen keine _verändernden_ Zustände, sondern rufen beobachtbare Ereignisse für den RHS-Wert auf. Es passiert einfach so, dass die Syntax, die Mobx gewählt hat, um diese beobachtbaren Ereignisse auszugeben, mit der ausdrücklichen Absicht von React unveränderlichen Zustand kollidiert.

@Iezious Wenn Sie eine schnelle Lösung für diese Art von Problem benötigen, können Sie damit durchkommen, indem Sie die React-Typisierungen erweitern und Ihre Komponenten so umgestalten, dass sie eine Erweiterung der Typdefinitionen React.Component verwenden.

import * as React from 'react';
declare module 'react' {
  class MutableStateComponent<P, S> extends React.Component<P, S> {
    state: S;
  }
}
(React as any).MutableStateComponent = React.Component;

und jetzt können Sie einfach veränderliche Zustandskomponenten wie normale Komponenten erstellen, außer dass ihr state Mitglied nicht mehr als readonly markiert ist.

export class MyComponent extends React.MutableStateComponent<MyProps, MyState> {
  // this.state.name is writable
}

@patsissons ja, genau das ist der grund.

Ich mutiere den Zustand nicht, ich verwende Mobx-Observables, die setState für mich aufrufen, ich mache es, da mein RIESIGER Projektcode viel klarer und verständlicher aussieht.

Ich weiß, dass ich meine Komponente erstellen kann, ich kann auch in meinem npm-Server ein Skript erstellen, das diese Änderung für unser Unternehmen immer rückgängig macht. Ich kann Hacks wie "this.state.state.updated" und viele andere Hacks verwenden.

Ich möchte nur sagen, dass sich diese Änderung auf die Verwendung von beobachtbaren Mustern wie Mobx auswirkt, die in Wirklichkeit dem Reaktionsweg folgen, aber jetzt, da diese Änderung nicht kompiliert werden kann und einige Hacks und Problemumgehungen benötigt, um zu funktionieren. Und deshalb denke ich, dass diese Änderung nicht richtig ist.

Vielleicht nennen wir es statt meines Vorschlags MutableStateComponent stattdessen ObservableComponent , was mehr auf das Observable React-Muster ausgerichtet ist.

Wenn Sie Readonly fallen lassen, dann sind Leute, die die React-Typen mit regulärem React (und/oder einer beliebigen Anzahl anderer State-Management-Systeme außer Mobx) verwenden, Fehlern ausgesetzt. Ich verwende Mobx nicht in meinem riesigen Projekt und ich schätze die Compilerfehler, wenn jemand einen Tippfehler macht und versehentlich this.state.foo = bar verwendet.

Wenn es einen unvermeidlichen Kompromiss zwischen der Verwendung von Standardreaktionen und Nicht-Standardreaktionen gibt, sollten sich die Standardreaktionstypen zu den ersteren neigen.

Darüber hinaus ist dies kein Problem, wenn Sie mobx auf idiomatische Weise verwenden.

Wenn Sie Readonly löschen, werden Personen, die die React-Typen mit Regular React (und/oder einer beliebigen Anzahl anderer State-Management-Systeme außer Mobx) verwenden, Fehlern ausgesetzt. Ich verwende Mobx nicht in meinem riesigen Projekt und schätze die Compilerfehler, wenn jemand einen Tippfehler macht und versehentlich this.state.foo = bar verwendet.

Also noch einmal – Sie LEHREN, DEN CODE ZU SCHREIBEN

Bei diesem Projekt geht es nicht darum, das Schreiben des Codes zu lehren, dieses Projekt soll die vorhandene Funktionalität von JS-Bibliotheken beschreiben. Die besprochene Einschränkung existiert nicht in der ursprünglichen Bibliothek und muss gelöscht werden.

Das ist alles.

@Patsissons

Wenn Sie eine schnelle Lösung für diese Art von Problem benötigen, können Sie damit durchkommen, indem Sie die React-Typisierungen erweitern und Ihre Komponenten so umgestalten, dass sie eine Erweiterung der Typdefinitionen von React.Component verwenden.

stimmt nicht. In der Unternehmenswelt, von wo aus ich bin, gibt es keine "schnellen Lösungen". Wenn sich etwas in SDKs ändert, muss oder muss es abwärtskompatibel sein, oder es dauert Jahre. Es gibt keine Schnellkorrekturen in mehr als 2 Millionen Codezeilen. Es sind Wochen voller Änderungen, Tests, A/B-Prod-Tests und Rollouts für eine große Anzahl von Menschen. Es kostet echtes Geld. Und all dieser enorme Aufwand, nur weil jemand eine nicht abwärtskompatible Änderung hinzugefügt hat, die IN REAL LIBRARY NICHT EXISTIERT?

Warum existiert das forceUpdate Ihrer Meinung nach immer noch in der Reaktion? Sie sprechen auf Confs über den richtigen Stil, nehmen aber die Änderungen vor, um die anderen Verwendungsweisen zu verhindern. Warum? Da es sich um ein großes Unternehmen handelt und sie wissen, dass Bibliotheken abwärtskompatibel sein müssen.

@ericanderson hat das geschrieben

this.state.state.value = 1 

ist aus seiner Sicht auch nicht legitim. Das nächste Mal wird er das Tool von TS erhalten und die Einschränkung hinzufügen, die diesen Code verhindert. Oder machen Sie eine Komponente zur letzten Klasse oder etwas anderes, nur weil "es der richtige Stil ist und die Leute daran hindert, Fehler zu machen".

Menschen an Mitakes hindern - es ist die Aufgabe von FB, wenn sie es tun wollen, können sie einfach einen Proxy hinzufügen und Zustandsmutationen verbieten. Der Zweck von DT besteht darin, Beschreibungen hinzuzufügen, und sonst nichts.

@Iezious

Ich denke, der Punkt ist, dass das React-Team den Zustand mit JavaScript nicht unveränderlich machen kann, aber wenn sie könnten, hätten sie es getan. TypeScript hingegen kann den Zustand unveränderlich machen, weshalb diese Änderung an den Typdefinitionen vorgenommen wurde. Es war absolut die Absicht des React-Teams, Änderungen an Zustandsmitgliedern direkt zu verbieten (wie aus ihren offiziellen Dokumenten zur korrekten Verwendung von Zustand hervorgeht ), aber sie hatten nicht die Sprachkonstrukte, um diese Einschränkung aufzuerlegen. Diese Einschränkung war nie eine Unbekannte, sie wurde von Anfang an gut dokumentiert. Für diejenigen von uns, die außerhalb der _konventionellen_ Nutzung von React arbeiten, müssen wir uns zumindest an die offiziellen Nutzungsempfehlungen von React halten. Für mein Team bedeutete das, eine Lösung zu entwerfen, die es uns ermöglichte, Zustandsänderungen voranzutreiben, ohne den Zustand direkt zu ändern. Dies wurde speziell getan, um sicherzustellen, dass wir nicht auf Probleme wie dieses stoßen würden (obwohl uns diese Änderung geringfügig beeinflusst hat, aber nur durch Funktionssignaturen und nicht durch grundlegendes Design).

Wenn ein Refactoring in Ihrer Situation nicht möglich ist, heften Sie entweder @types/react an 15.0.1 an, bevor die Änderung vorgenommen wurde, oder verwenden Sie einfach nicht @types/react und behalten Sie stattdessen Ihre eigene Variante von bei Geben Sie defs ein und ändern Sie einfach die state Eingabe für Component . Ich glaube wirklich nicht, dass Sie Erfolg haben werden, jemanden davon zu überzeugen, die Änderung rückgängig zu machen.

forceUpdate ist vorhanden, weil es als empfohlener Codepfad zum Steuern des Renderns dokumentiert ist, wenn der interne Strukturstatus geändert wird (oder wenn render() Daten außerhalb des Status verwendet, die veränderbar sind). Die Verwendung von forceUpdate ist genau auf das Nutzungsmuster ausgelegt, das mein Team mit React verwendet.

Das Reaktionsteam KANN den Zustand mit JS unveränderlich machen, es ist ganz einfach. Aber nicht abwärtskompatibel.

Noch einmal ist:

this.state.state.value = 1 

legitim oder nicht?

Ich denke schon, aber meine Meinung ist schon klar...

Ich glaube nicht, dass das Gespräch über Veränderlichkeit / Unveränderlichkeit _noch_ relevant ist. Der Fehler im TS-Compiler (in Bezug auf Readonly ) in Kombination mit dieser Änderung macht es eindeutig unmöglich, generische Komponenten zu verwenden, wodurch viel bestehender Code beschädigt wird. Sicherlich ist das ein allgemein akzeptierter gültiger Anwendungsfall und Grund genug, dies vorerst rückgängig zu machen???

interface test1 {
    num: number;
}

function add<T extends test1>(initial: T, add: number) {
    var copy: Readonly<T> = initial;

    //ERROR HERE: [ts] Operator '+' cannot be applied to types 'T["num"]' and 'number'.
    return copy.num + add;
}

Weiß jemand, ob es diesbezüglich ein offenes Problem mit dem Typescript-Team gibt? Ich kann das relevante Problem anscheinend nicht auf ihrem Tracker finden.

@caesay Siehe Microsoft/TypeScript#15501

Ich muss den Zustand im Konstruktor initialisieren, aber tslint zeigt "Zustand ist schreibgeschützt"....

constructor(props) {
  super(props);
  this.state = {
     value: props.defaultValue,
  };
}

wie kann ich es reparieren...............

Verwenden Sie setState

setState funktioniert nicht im Konstruktor

Oder betrachten Sie componentWillMount w/setState

Danke

Hallo,

Ich habe den ganzen Thread gelesen, aber es ist mir nicht klar, ob es Pläne gibt, das @alanwei0- Szenario zu behandeln?

Ich stimme voll und ganz zu, dass es sinnvoll ist, state als ReadOnly zu haben. Davon abgesehen, dass es nicht möglich ist, den Anfangszustand im Konstruktor festzulegen, verkompliziert die Dinge ziemlich, da render aufgerufen wird, bevor componentDidMount fertig ist.

@pawelpabich mit this.state = { im Konstruktor ist kein Problem. Sie können readonly Variablen im Konstruktor zuweisen, und Readonly<T> verhindert die Zuweisung nicht (niemals!).

interface TInterface {
    test: string;
}

class TClass {
    readonly blah: Readonly<TInterface>;
    constructor() {
        this.blah = { test: "constructor" };
    }

    fn = () => {
        this.blah = { test: "fn" };
    }
}

Der einzige Fehler hier wird innerhalb fn sein – nicht wegen Readonly<T> , sondern wegen des Schlüsselworts readonly . Tatsächlich verwendet die neueste Version der Typisierungen nicht einmal das Schlüsselwort readonly , sodass Sie den Zustand tatsächlich überall zuweisen können, nur keine Eigenschaften innerhalb des Zustands ändern.

Das Problem, über das wir hier gesprochen haben, war ein Fehler im Typoskript-Compiler, der dazu führte, dass die Zustandseigenschaften ihre Typen in geerbten Komponenten verloren. Dies wurde meiner Meinung nach inzwischen behoben, und wenn ja, kann dieses Problem geschlossen werden.

@caesay Entschuldigung, ich dachte, das wäre der Fall, über den wir hier sprechen. Mein Problem ist, dass ich den Zustand in der Basisklasse nicht festlegen kann. Ich bin auf TS 2.4.1. Hast du zufällig die ID des Problems, damit ich das überprüfen kann?

Sie können immer setState (in componentWillMount) aufrufen.

@pawelpabich Auch dies ist kein Problem :) Sie können den Zustand nicht absichtlich aus der Basisklasse zuweisen. Wie konntest du? Sie kennen den Prop-Vertrag nicht, der von der abgeleiteten Komponente verwendet wird.

interface BaseCompState {
    baseState1?: string;
}

class BaseComp<TState extends BaseCompState> extends React.Component<any, TState> {
    constructor(props) {
        super(props);
        this.state = {
            baseState1: "fromBase",
        };
    }
}

interface TopCompState extends BaseCompState {
    topState1?: string;
}

class TopComp extends BaseComp<TopCompState> {
    constructor(props) {
        super(props);
        this.state = {
            baseState1: "fromTop",
            topState1: "fromTop",
        };
    }
}

Das ist ein einfaches Beispiel für eine abgeleitete Komponente (Requisiten weggelassen, aber dieselbe Idee). das this.state = in der Basisklasse kann offensichtlich nicht funktionieren, weil es nicht weiß, was TState ist. außerdem würde es, wenn es _funktionieren_ würde, nur den Zustand überschreiben, der vom Elternteil gesetzt wurde. Der Endzustand wäre { baseState1: "fronBase" } . Was ist mit der Eigenschaft topState passiert?

Wenn Sie absolut davon überzeugt sind, dass Sie den Status in Ihrer Basiskomponente handhaben müssen, können Sie den Status von der abgeleiteten Komponente an den Konstruktor der Basiskomponente übergeben (als TState , damit Sie ihn zuweisen können) – das könnte in etwa so aussehen Das:

interface BaseCompState {
    baseState1?: string;
}

class BaseComp<TState extends BaseCompState> extends React.Component<any, TState> {
    constructor(props, state: TState) {
        super(props);
        this.state = Object.assign({
            baseState1: "fromTop",
        }, state);
    }
}

interface TopCompState extends BaseCompState {
    topState1?: string;
}

class TopComp extends BaseComp<TopCompState> {
    constructor(props) {
        super(props, {
            topState1: "fromTop",
        });
    }
}

Oder noch einfacher, Sie könnten this.setState( innerhalb Ihrer Basiskomponente aufrufen (ja, das können Sie im Konstruktor tun!)

Hier gibt es kein Problem.

@caesay Ich stimme voll und ganz zu, dass die Zuordnung keinen Sinn ergibt, wenn es keine Einschränkungen gibt. Der folgende Code generiert jedoch immer noch Kompilierungsfehler, obwohl der Compiler über alle erforderlichen Informationen verfügt, um zu überprüfen, ob der Code korrekt ist.

import * as React from "react";

/* tslint:disable:max-classes-per-file*/

interface BaseProps {
    baseProp: string;
}

interface BaseState {
    baseState: string;
}

class Base<TProps extends BaseProps, TState extends BaseState> extends React.Component<TProps, TState> {
    constructor(props) {
        super(props);

        this.state = {
            baseState: props.baseProp
        };
    }

    render() {
        return <div>{this.state.baseState}</div>;
    }
}

interface DerivedProps extends BaseProps {
    derivedProp: string;
}

interface DerivedState extends BaseState {
    derivedState: string;
}

export class Derived extends Base<DerivedProps, DerivedState> {
    constructor(props) {
        super(props);

        this.state = {
            derivedState: props.derivedProp
        };
    }

    render() {
        return <div>{this.state.derivedState}</div>;
    }
}

Fehler

webpack: Compiled successfully.
ERROR at Test.tsx(17,9):
TS2322: Type '{ baseState: any; }' is not assignable to type 'Readonly<TState>'.

ERROR at Test.tsx(39,9):
TS2322: Type '{ derivedState: any; }' is not assignable to type 'Readonly<DerivedState>'.
  Property 'baseState' is missing in type '{ derivedState: any; }'.

Version: typescript 2.4.1

Zuerst. Ihre Requisiten in der Basis im Konstruktor sind nicht typisiert. Somit ist props.baseProp any , was nicht zuweisbar ist!

Zweitens haben Ihre Requisiten in Derived das gleiche Problem UND Ihnen fehlt baseState . Was natürlich nicht funktioniert, unabhängig von Readonly

TProps extends BaseProps was bedeutet, dass TProps mindestens die gleichen Mitglieder hat wie BaseProps . Also wie ist das nicht definiert? Ich verstehe, dass der Compiler möglicherweise nicht in der Lage ist, darauf zu schließen, aber zu sagen, dass dies nicht definiert ist, scheint nicht richtig zu sein. Dasselbe gilt für Derived .

@caesay setState im Konstruktor ist keine zuverlässige Lösung, da diese Methode asynchron ist und Sie immer noch zu render gelangen können, ohne den Anfangszustand festzulegen.

Die einzige zuverlässige Lösung, die ich sehen kann, besteht darin, den gesamten Zustand in den abgeleiteten Klassen festzulegen. Dies hat einen offensichtlichen Nachteil:

  1. Dies muss in jeder abgeleiteten Klasse dupliziert werden
  2. Abgeleitete Klassen müssen den Zustand kennen, der ihnen egal ist.

Das Beispiel, das ich oben gezeigt habe, würde in C# funktionieren, also wäre es schön, wenn es in TypeScript funktionieren könnte.

  1. ~setState ist sicher im Konstruktor~
  2. Im Fall einer abgeleiteten Klasse ist der beste Fall, den ich sehen kann, setState in Ihrem componentWillMount aufzurufen. Ihre Basis weiß nicht, was in dem Zustand für ihr untergeordnetes Element sein sollte, daher kann sie unmöglich this.state in eine sichere Konfiguration bringen. Ihre Unterklasse kann jedoch das componentWillMount der übergeordneten Klasse aufrufen und dann den Zustand festlegen, den sie ihrer Meinung nach ebenfalls benötigt.
  3. Es gibt Sprachfeatures in vielen Sprachen, die man gerne in Maschinenschrift hätte. Einige sind machbar. Andere sind es nicht. So oder so ist es kein Argument für ihre Aufnahme.
  4. Die Fehler, die Sie sehen, machen Sinn. Sie schlagen weder einen Fehler im Typoskript noch in den Typisierungen vor. In jedem Fall versuchen Sie buchstäblich, this.state einem Objekt zuzuweisen, das nicht dem erwarteten Typ entspricht.

BEARBEITET, um widerzuspiegeln, dass ich mich in Bezug auf setState im Konstruktor geirrt habe, und um Backticks für die Lesbarkeit hinzuzufügen.

@ericanderson Ich glaube nicht, dass wir hier Fortschritte machen. Ich habe Ihnen ein Beispiel gezeigt, und ich würde es begrüßen, wenn Sie sich darauf konzentrieren könnten. Sonst ist eine Diskussion schwierig.

Bezüglich setState ist die Verwendung im Konstruktor nicht sicher. Es gibt keinen Fehler aus, aber es wird den Status nicht festlegen, bevor das Rendern erfolgt. Ich habe Code, der aus diesem Grund kaputt geht, und React-Dokumente sind sehr klar, dass er dort nicht verwendet werden sollte.

@pawelpabich Nein, dies ist nicht der Ort, um dieses Argument zu führen. Du liegst mit deinem Sprachverständnis grundlegend falsch. In dem von Ihnen angegebenen Beispiel haben Sie den „Staats“-Vertrag in KEINER Ihrer Zuweisungen an den Staat erfüllt.

Zum Beispiel, wenn Sie es tun

this.state = { baseState: props.baseProp };
// now the state is exactly { baseState: props.baseProp }

Der Zustand ist genau { baseState: props.baseProp } und dies erfüllt NICHT die Anforderung von TProps extends BaseProps (weil wir nicht wissen, was TProps ist! es könnte irgendwelche Eigenschaften enthalten).

Danach machst du eine _ SEPARATE _ Aufgabe.

this.state = { derivedState: props.derivedProp };
// now the state is exactly {  derivedState: props.derivedProp }

der Status ist jetzt genau { derivedState: props.derivedProp } (Sie haben Ihre Basisstatuszuweisung überschrieben!!) und dies erfüllt DerivedState ODER BaseProps nicht.

Du irrst dich total, wenn du denkst, dass das funktionieren sollte. Wenn Sie ein Problem mit der Funktionsweise grundlegender Variablenzuweisungen haben, wenden Sie sich an die Sprachdesigner in einer neuen Ausgabe und werden dort abgeschossen, damit wir hier bitte keine Benachrichtigungen mehr erhalten.

Als Randnotiz - Sie überschreiben AUCH Ihre Basismethode render() , was bedeutet, dass Ihre Basiskomponente nichts rendern kann. Wenn Sie davon überzeugt sind, dass dies das ist, was Sie wollen, können Sie ein geschütztes Element getBaseState() hinzufügen und dieses vom abgeleiteten Konstruktor aufrufen, wenn Sie den Zustand festlegen (damit Sie die Basiszustandslogik nicht duplizieren müssen). Aber was Sie meiner Meinung nach wirklich wollen, ist, überhaupt keine abgeleiteten Komponenten zu verwenden. Versuchen Sie eine Umstrukturierung, um die Komposition zu verwenden (wobei Sie ein Objekt mit mehreren untergeordneten Objekten haben). Ich denke, Sie werden feststellen, dass es viel sauberer ausfallen wird.

Ich möchte mich kaum vom Lesen abwenden, um ein neues Projekt zu erstellen, nur um dies mit Ihnen zu diskutieren, aber leider ...

Ich werde bezüglich setState () im Konstruktor korrigiert stehen, aber das ändert nichts daran, wie ich es in componentWillMount verwende.

Arbeitsbeispiel, wie dies geschehen würde:
https://github.com/ericanderson/set-state-example

Insbesondere index.tsx:

import * as React from "react";
import * as ReactDOM from "react-dom";

/* tslint:disable:max-classes-per-file*/

interface BaseProps {
  baseProp: string;
}

interface BaseState {
  baseState: string;
}

class Base<TProps extends BaseProps, TState extends BaseState> extends React.Component<TProps, TState> {
  public componentWillMount() {
    this.setState({
      baseState: this.props.baseProp,
    });
  }

  public render() {
    return (
      <p>
        <code>this.state.baseState: </code>
        {this.state.baseState}
      </p>
    );
  }
}

interface DerivedProps extends BaseProps {
  derivedProp: string;
}

interface DerivedState extends BaseState {
  derivedState: string;
}

export class Derived extends Base<DerivedProps, DerivedState> {
  public componentWillMount() {
    super.componentWillMount();
    this.setState({
      derivedState: this.props.derivedProp,
    });
  }

  public render() {
    return (
      <div>
        <p>
          <code>this.state.derivedState: </code>
          {this.state.derivedState}
        </p>
        {super.render()}
      </div>
    );
  }
}

ReactDOM.render(<Derived derivedProp="its derived" baseProp="its basic" />, document.getElementById("main"));

@pawelpabich Wenn Sie eine polymorphe Komponente mit einem Anfangszustand implementieren möchten, müssen Sie Ihre Basiskomponente abstrakt machen und eine abstrakte getInitialState() -Funktion (oder eine ähnlich thematisierte) Funktion erstellen, die Sie in Ihrer abgeleiteten Klasse implementieren können. Sie möchten den Zustand nur einmal zuweisen, entweder im Konstruktor oder mit setState , wie @ericanderson gezeigt hat.

Unten ist Ihr Beispiel, das in eine vollständig polymorphe Lösung umgewandelt wurde, mit vollständiger Trennung der Bedenken in Bezug auf die Zustandskonstruktion:

interface BaseProps {
  baseProp: string;
}

interface BaseState {
  baseState: string;
}

abstract class Base<TProps extends BaseProps, TState extends BaseState> extends React.Component<TProps, TState> {
  constructor(props: TProps) {
      super(props);

      this.state = this.getInitialState();
  }

  protected abstract getInitialState(): TState;

  protected getBaseState() {
    return this.props.baseProp;
  }

  render() {
      return <div>{this.state.baseState}</div>;
  }
}

interface DerivedProps extends BaseProps {
  derivedProp: string;
}

interface DerivedState extends BaseState {
  derivedState: string;
}

export class Derived extends Base<DerivedProps, DerivedState> {
  getInitialState(): DerivedState {
    return {
      baseState: this.getBaseState(),
      derivedState: this.props.derivedProp,
    };
  }

  render() {
      return <div>{this.state.derivedState}</div>;
  }
}

@patsissons danke!

@caesay Ich gebe zu, dass ich mich geirrt habe und aus irgendeinem Grund nicht gesehen habe, dass sich die Zuweisungen gegenseitig überschreiben. Abgesehen davon half mir die Verwendung von CAPS und ! nicht, aus meinem Loch herauszukommen.

@patsissons und @ericanderson haben sich auf das Problem konzentriert und jetzt haben wir eine Lösung, die andere verwenden können.

@pawelpabich Ich stimme zu, dass meine Manierismen alles andere als professionell waren - aber verständlicherweise, wenn man bedenkt, dass ich Ihnen mehrere Erklärungen, Beispiele usw. gegeben habe und Sie sich entschieden haben, mir nicht zuzuhören.

dann würde es nur den vom Elternteil festgelegten Zustand überschreiben.

[_wenn Sie_] den Status in Ihrer Basiskomponente behandeln möchten, könnten Sie den Status von der abgeleiteten Komponente an den Konstruktor der Basiskomponente übergeben

[_wenn Sie den Status in Ihrer abgeleiteten Komponente behandeln möchten_] können Sie ein geschütztes Element getBaseState() hinzufügen und dieses vom abgeleiteten Konstruktor aufrufen, wenn Sie den Status festlegen.

Was @patsissons getan hat, war, die bereits hier erwähnten Kommentare zu nehmen und ein Codebeispiel bereitzustellen - was nicht notwendig gewesen sein sollte. Dies ist kein Stapelüberlauf, und wir stellen dort auch nicht oft fertige Codebeispiele zur Verfügung.

Ich bin neu im Reagieren und Schreiben, vielleicht weiß ich etw nicht, aber obwohl die App ohne Fehler, Warnung und Hinweis kompiliert wird, erhalte ich einen Laufzeitfehler. Unten ist eine Beispielkomponente. Ich schreibe den Fehler dem Zustand Readonly zu. Wenn die App vor der Readonly -Änderung funktioniert hat, funktioniert sie nach dieser Änderung nicht mehr und gibt keine Kompilierzeitfehler mehr aus.

import * as React from 'react';

export default class HomePage extends React.Component<any, Map<string, string>> {

  public componentWillMount() {
    const map = new Map<string, string>();
    map.set('aKey', 'aValue');
    this.setState(map);
  }

  public render() {

      return (
        <div className="home">
          <div className="greeting">
            Home page: {this.state.get('aKey')} // <-- I get an error here
          </div>
        </div>
      );
  }
}

Der Fehler:

homePage.tsx:12 Uncaught TypeError: this.state.get is not a function
    at HomePage.render (homePage.tsx:12)
    at eval (ReactCompositeComponent.js:793)
    at measureLifeCyclePerf (ReactCompositeComponent.js:73)
    at ReactCompositeComponentWrapper._renderValidatedComponentWithoutOwnerOrContext (

State sollte afaik immer ein Objekt mit einfachem Schlüssel sein, also definieren Sie stattdessen state
als so etwas wie: { values: Map <string, string> } und lesen
this.state.values.get('aKey')

Op vr 29.9. 2017 um 09:01 schreef Janusz Białobrzewski <
[email protected]>:

Ich bin neu im Reagieren und Schreiben, vielleicht weiß ich etw nicht, aber eben
Obwohl die App ohne Fehler, Warnung und Hinweis kompiliert wird, erhalte ich eine Laufzeit
Error. Unten ist eine Beispielkomponente. Ich führe den Fehler auf Readonly zurück
Zustand. Wenn die App vor der Readonly-Änderung funktioniert hat, dann danach
ändern, es funktioniert nicht mehr und es gibt keine Kompilierzeitfehler.

import * als React von 'react';
export default class HomePage erweitert React.Component> {

öffentliche KomponenteWillMount() {
const map = neue Karte();
map.set('aKey', 'aValue');
this.setState (Karte);
}

öffentliches rendern () {

  return (
    <div className="home">
      <div className="greeting">
        Home page: {this.state.get('aKey')} // <-- I get an error here
      </div>
    </div>
  );

}
}

Der Fehler:

Startseite. tsx:12 Nicht erfasster TypeError: this.state.get ist keine Funktion
bei HomePage.render (homePage.tsx:12)
bei eval (ReactCompositeComponent.js:793)
bei measureLifeCyclePerf (ReactCompositeComponent.js:73)
bei ReactCompositeComponentWrapper._renderValidatedComponentWithoutOwnerOrContext (


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/14250#issuecomment-333047367 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/ABvGhM5hDyRNyUeZuIiGeTZk1N-rfuA4ks5snJW5gaJpZM4LuDWV
.

Danke, aber es scheint ein sinnloser Versuch zu sein, state als Readonly<S> zu deklarieren, da seine verschachtelten Mitglieder nicht von Readonly betroffen sind.

Es ist möglich, dass Readonly eines Tages rekursiv angewendet wird, aber im Moment müssen Sie sicherstellen, dass Sie es richtig handhaben. In Ihrem Fall sollten Sie wirklich ReadonlyMap deklarieren oder

interface State {
    readonly [key: string]: string;
}

oder verschachtelt:

interface State {
    map: { readonly [key: string]: string };
}

Wir können es nur für Deep Read verwenden:

export type DeepReadonly<T> =
  T extends Array<any> ?
  ReadonlyArray<T[0]> :
  T extends Date ?
  T :
  T extends Function ?
  T :
  T extends object ?
  { readonly [P in keyof T]: DeepReadonly<T[P]> } :
  T;

export type Writable<T> =
  T extends ReadonlyArray<any> ?
  Array<WritableObject<T[0]>> :
  T extends Array<any> ?
  Array<WritableObject<T[0]>> :
  WritableObject<T>;

type WritableObject<T> =
  T extends Date ?
  T :
  T extends Function ?
  T :
  T extends object ?
  { -readonly [P in keyof T]: Writable<T[P]> } :
  T;
War diese Seite hilfreich?
0 / 5 - 0 Bewertungen