Definitelytyped: [@types/react] RefObject.current sollte nicht mehr schreibgeschützt sein

Erstellt am 5. Dez. 2018  ·  48Kommentare  ·  Quelle: DefinitelyTyped/DefinitelyTyped

Es ist jetzt in Ordnung, ref.current zuzuweisen, siehe Beispiel: https://reactjs.org/docs/hooks-faq.html#is -there-something-like-instance-variables

Der Versuch, ihm einen Wert zuzuweisen, ergibt den Fehler: Cannot assign to 'current' because it is a constant or a read-only property.

  • [x] Ich habe versucht, das @types/react -Paket zu verwenden, und hatte Probleme.
  • [x] Ich habe versucht, die neueste stabile Version von tsc zu verwenden. https://www.npmjs.com/package/typescript
  • [x] Ich habe eine Frage, die für StackOverflow ungeeignet ist. (Bitte stellen Sie dort entsprechende Fragen).
  • [x] [Erwähnen](https://github.com/blog/821-mention-somebody-they-re-notified) die Autoren (siehe Definitions by: in index.d.ts ), damit sie es können Antworten.

    • Autoren: @johnnyreilly @bbenezech @pzavolinsky @digiguru @ericanderson @morcerf @tkrotoff @DovydasNavickas @onigoetz @theruther4d @guilhermehubner @ferdaber @jrakotoharisoa @pascaloliv @Hotell @franklixuefei

Hilfreichster Kommentar

Es ist nicht. Es ist absichtlich schreibgeschützt, um eine korrekte Verwendung sicherzustellen, auch wenn es nicht eingefroren ist. Refs, die mit null initialisiert wurden, ohne ausdrücklich anzugeben, dass Sie null zuweisen möchten, werden als Refs interpretiert, die von React verwaltet werden sollen – dh React „besitzt“ den aktuellen und Sie sehen ihn nur.

Wenn Sie ein änderbares Referenzobjekt wünschen, das mit einem Nullwert beginnt, stellen Sie sicher, dass Sie dem generischen Argument auch | null geben. Das macht es veränderlich, weil Sie es "besitzen" und nicht reagieren.

Vielleicht ist das für mich einfacher zu verstehen, da ich schon oft mit zeigerbasierten Sprachen gearbeitet habe und der Besitz in ihnen _sehr_ wichtig ist. Und das ist es, was Refs sind, ein Zeiger. .current dereferenziert den Zeiger.

Alle 48 Kommentare

Eine PN wäre wünschenswert! Es sollte ein sehr einfacher Zeilenwechsel sein 😊

@Ferdaber ,
Hallo, ich greife gerne dieses Thema auf. Da ich neu in Typoskript bin (nur eine Woche) und es mein erster Beitrag zu Open Source sein wird.

Ich bin node_modules/@types/react/index.d.ts durchgegangen und es gibt den folgenden Code in Zeile Nr. 61, der den aktuellen als schreibgeschützt deklariert.

interface RefObject<T> {
        readonly current: T | null;
    }

Wenn dies das Problem ist, kann ich es lösen. Können Sie mich für meine erste PR anleiten?
Vielen Dank für Ihre Zeit :)

Ja, es entfernt nur den Modifikator readonly in dieser Zeile.

React.useRef gibt ein MutableRefObject zurück, dessen Eigenschaft current nicht readonly ist. Ich nehme an, die Frage ist, ob die Ref-Objekttypen vereinheitlicht werden sollten.

Sie sollten vereinheitlicht werden, die React-Laufzeit hat keine Beschränkung auf die Eigenschaft current , die von React.createRef() erstellt wurde, das Objekt selbst ist versiegelt, aber nicht eingefroren.

Es ist nicht. Es ist absichtlich schreibgeschützt, um eine korrekte Verwendung sicherzustellen, auch wenn es nicht eingefroren ist. Refs, die mit null initialisiert wurden, ohne ausdrücklich anzugeben, dass Sie null zuweisen möchten, werden als Refs interpretiert, die von React verwaltet werden sollen – dh React „besitzt“ den aktuellen und Sie sehen ihn nur.

Wenn Sie ein änderbares Referenzobjekt wünschen, das mit einem Nullwert beginnt, stellen Sie sicher, dass Sie dem generischen Argument auch | null geben. Das macht es veränderlich, weil Sie es "besitzen" und nicht reagieren.

Vielleicht ist das für mich einfacher zu verstehen, da ich schon oft mit zeigerbasierten Sprachen gearbeitet habe und der Besitz in ihnen _sehr_ wichtig ist. Und das ist es, was Refs sind, ein Zeiger. .current dereferenziert den Zeiger.

Das ist fair, ich habe React.createRef() als Hilfsfunktion verwendet, um einfach einen Zeiger zu erstellen, da React es nicht verwalten wird, es sei denn, es wird als ref Prop übergeben, aber Sie können es genauso Erstellen Sie nun ein einfaches Zeigerobjekt mit derselben Struktur.

Wir könnten createRef so modifizieren, dass es dieselbe Überladungslogik hat, aber da es kein Argument gibt, müsste es mit einer bedingten Typrückgabe erfolgen. Wenn Sie | null einschließen, würde es MutableRefObject zurückgeben, wenn Sie es nicht tun, wäre es (unveränderlich) RefObject .

Würde das aber für diejenigen funktionieren, die strictNullTypes nicht aktiviert haben?

🤔

Sollten wir es wirklich einfacher machen, falschen Code für diejenigen zu schreiben, die _do_ strictNullTypes aktiviert haben?

Als ich dieses Problem erstellte, war mir nicht klar, dass diese | null -Überlastung bereits vorhanden war. Es ist kein sehr häufiges Muster, also habe ich nicht erwartet, dass so etwas existiert. Ich weiß nicht, wie ich es in der Dokumentation übersehen habe, es ist sehr gut dokumentiert und erklärt. Ich kann dies als kein Problem schließen, es sei denn, Sie möchten useRef und createRef vereinheitlichen.

Da das Problem selbst auf useRef und nicht createRef basierte, bin ich auch damit einverstanden, dies zu schließen, da nur sehr wenige Leute da draußen den dereferenzierten Zeigerwert in createRef direkt ändern.

Ich frage mich jedoch, ob dieses Überladungsmuster gut funktionieren wird. Wenn wir uns die Hooks-Dokumentation ansehen, sehen wir einige Verwendungen von useRef mit einem Standardwert, in welchem ​​​​Fall der Wert von .current möglicherweise nie der Fall ist null, aber es kann für Benutzer ziemlich lästig sein, ständig den Nicht-Null-Assertion-Operator zu verwenden, um daran vorbeizukommen.

Wäre es sinnvoller, den Rückgabewert veränderlich zu machen, wenn ein Anfangswert angegeben wird, aber unveränderlich, wenn er weggelassen wird? Wenn ein Anfangswert angegeben wird, wird die Referenz wahrscheinlich nicht über ref an eine Komponente übergeben und eher wie eine Instanzvariable verwendet. Wenn Sie andererseits eine Referenz erstellen, die nur an eine Komponente übergeben werden soll, wird wahrscheinlich kein Anfangswert bereitgestellt.

So ungefähr dachte ich. @Kovensky , was denkst du?

@ferdaber useRef , wie es jetzt definiert ist, ist immer änderbar _und_ nicht nullbar, es sei denn, Sie geben ihm explizit ein generisches Argument, das | null nicht enthält, und einen Anfangswert von null .

Refs, die an eine Komponente übergeben werden, sollten mit null initialisiert werden, da React Refs darauf setzt, wenn sie freigegeben werden (zB wenn eine bedingt gemountete Komponente ausgehängt wird). useRef ohne Übergabe eines Wertes würde dazu führen, dass es stattdessen mit undefined beginnt, also müssten Sie jetzt auch | undefined zu etwas hinzufügen, das sich nur um | null kümmern müsste

Das Problem in der Dokumentation von React und der Verwendung useRef ohne einen Anfangswert ist, dass das React-Team sich nicht allzu sehr um den Unterschied zwischen null und undefined kümmert. Das könnte eine Flow-Sache sein.


Wie auch immer, die Art und Weise, wie ich useRef definiert habe, passt genau zum Anwendungsfall von @bschlenk , nur dass null aus den oben genannten Gründen der "ausgelassene" Wert ist.

Ah, genaueres Hinsehen zeigt das, und es ist interessant, dass es als undefined in der React-Quelle ohne Parameter initialisiert wird. Cool, also ist alles pfirsichfarben, es klingt wie 👍

Ich denke, es ist in Ordnung, wie es ist, nur ein bisschen seltsam, dass, ob Sie | null , der Typ von current immer noch null sein kann, nur die Veränderlichkeit hat sich geändert. Außerdem übergeben die React-Dokumente immer explizit null, ist es also überhaupt gültig, den initialValue wegzulassen?

Im Allgemeinen nicht, und zumindest in der Vergangenheit, als ich darauf hinwies, dass das React-Team in einer Dokumentation sagte, dass "selbst wenn es funktioniert", es nicht weggelassen werden soll.

Aus diesem Grund ist beispielsweise das erste Argument für createContext erforderlich, auch wenn Sie möchten, dass ein Kontext mit undefined beginnt. Sie müssen tatsächlich undefined passieren.

Sieht so aus, als ob in einigen Verwendungen der Parameter nicht verwendet wird:

https://reactjs.org/docs/hooks-faq.html#is -es gibt so etwas wie Instanzvariablen
https://reactjs.org/docs/hooks-faq.html#how -to-get-the- previous-props-or-state
https://reactjs.org/docs/hooks-faq.html#how -to-read-an-often-changing-value-from-usecallback

@ferdaber das liegt daran, dass sie sich dort nicht um Typen kümmern. Was sollte der Typ von current in einem useRef() (ohne Argumente) sein? undefined ? any ? Was auch immer es ist, selbst wenn Sie ein generisches Argument angeben, muss es | mit undefined sein. _Und_ wenn es sich um eine Referenz handelt, die Sie als React-Element-Referenz verwenden (nicht nur als lokale Thread-Speicherung), müssen Sie auch | null angeben, da React dort möglicherweise eine null schreibt.

Jetzt sind current T | null | undefined statt nur T .

// you should always give an argument even if the docs sometimes miss them
// $ExpectError
const ref1 = useRef()
// this is a mutable ref but you can only assign `null` to it
const ref2 = useRef(null)
// this is also a mutable ref but you can only assign `undefined`
const ref3 = useRef(undefined)
// this is a mutable ref of number
const ref4 = useRef(0)
// this is a mutable ref of number | null
const ref5 = useRef<number | null>(null)
// this is a mutable ref with an object
const ref6 = useRef<React.CSSProperties>({})
// this is a mutable ref that can hold an HTMLElement
const ref7 = useRef<HTMLElement | null>(null)
// this is the only case where the ref is immutable
// you did not say in the generic argument you want to be able to write
// null into it, but you gave a null anyway.
// I am taking this as the sign that this ref is intended
// to be used as an element ref (i.e. owned by React, you're only sharing)
const ref8 = useRef<HTMLElement>(null)
// not allowed, because you didn't say you want to write undefined in it
// this is essentially what would happen if we allowed useRef with no arguments
// to make it worse, you can't use it as an element ref, because
// React might write a null into it anyway.
// $ExpectError
const ref9 = useRef<HTMLElement>(undefined)

Ja, dieses DX funktioniert für mich, und die Dokumentation ist A++, also glaube ich nicht, dass die meisten Leute davon verwirrt werden. Danke für diese Ausarbeitung! Ehrlich gesagt sollten Sie dieses Problem einfach im Typedoc verlinken :)

Es könnte einen Grund geben, useRef<T>() zu unterstützen und dafür zu sorgen, dass es sich genauso verhält wie useRef<T | undefined>(undefined) ; aber was auch immer Sie damit machen, es kann immer noch nicht als Element-Referenz verwendet werden, genauso wenig wie als lokaler Thread-Speicher.

Das Problem ist... was passiert, wenn Sie _kein_ generisches Argument angeben, was erlaubt ist? TypeScript wird einfach {} ableiten. Der korrekte Standardtyp ist unknown , aber wir können ihn nicht verwenden.

Ich erhalte diesen Fehler mit dem folgenden Code:

~~~js
// ...
Lassen Sie intervalRef = useRef(Null); // auch versucht mit const statt let
// ...
useEffect( () => {
const interval = setInterval( () => { /* etwas tun */}, 1000);
intervalRef.current = Intervall; // In dieser Zeile bekomme ich den Fehler

return () => {
    clearInterval(intervalRef.current);
}

})
// ...
~Und wenn ich hier das readonly entferne, funktioniert es:~ js
Schnittstelle RefObject{
Nur-Lese-Strom: T | Null;
}
~~~

Ich bin sowohl mit Reack-Hooks als auch mit Typoskript neu (probiere sie nur zusammen aus), also könnte mein Code falsch sein

Wenn Sie eine Referenz mit einem null -Standardwert erstellen und ihren generischen Parameter angeben, signalisieren Sie standardmäßig Ihre Absicht, dass React die Referenz "besitzt". Wenn Sie in der Lage sein möchten, einen Ref zu mutieren, den Sie besitzen, sollten Sie ihn folgendermaßen deklarieren:

const intervalRef= useRef<NodeJS.Timeout | null>(null) // <-- the generic type is NodeJS.Timeout | null

@ferdaber Danke dafür!

Gehen wir noch einen Schritt weiter und betrachten den Rückgabetyp von current , sollte es vielleicht T statt T | null sein? Mit dem Aufkommen von Hooks haben wir _nicht immer_ den Fall, dass alle Refs null sein könnten, insbesondere in dem häufigen Fall, dass useRef mit einem Nicht-Null-Initialisierer aufgerufen wird.

Fortsetzung der ausgezeichneten Beispielliste in https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065#issuecomment -446660394, wenn ich schreibe:

const numericRef = useRef<number>(42);

was sollte der Typ von numericRef.current sein? Es gibt keine _Notwendigkeit_ dafür, dass es number | null ist.

Wenn wir die Typen und Funktionen wie folgt definieren:

interface RefObject<T> {
  current: T;
}

function useRef<T>(initialValue: T): RefObject<T>;
function useRef<T>(initialValue: T|null): RefObject<T | null>;

function createRef<T>(): RefObject<T | null>;

das würde die folgenden Verwendungen und Typen erzeugen:

const r1 = useRef(42);
// r1 is of type RefObject<number>
// r1.current is of type number (not number | null)

const r2 = useRef<number>(null);
// r2 is of type RefObject<number | null>
// r2.current is of type number | null

const r3 = useRef(null);
// r3 is of type RefObject<null>
// r3.current is of type null

const r4 = createRef<number>();
// r4 is of type RefObject<number | null>
// r4.current is of type number | null

Ist da etwas falsch?

Für die Antwort auf „Was ist der Typ eines nicht parametrisierten useRef ?“ lautet die Antwort, dass dieser Aufruf (trotz der Dokumentation) laut dem React-Team falsch ist.

Ich habe die Überladung mit |null _speziell_ als bequeme Überladung für DOM/Komponenten-Referenzen hinzugefügt, da sie immer mit null beginnen, beim Aushängen immer auf null zurückgesetzt werden und Sie den Strom nie selbst neu zuweisen, sondern nur reagieren.

Readonly dient eher dem Schutz vor logischen Fehlern als einer Darstellung eines echten eingefrorenen JavaScript-Objekts / einer Nur-Getter-Eigenschaft.

Sie können es nur versehentlich machen, wenn Sie beide ein generisches Argument angeben, das besagt, dass Sie null nicht akzeptieren, während Sie den Wert trotzdem auf null initialisieren. Jeder andere Fall ist änderbar.

Ah, ja, ich sehe jetzt, dass MutableRefObject<T> im Vergleich zu RefObject<T> $ bereits der Fall | null entfernt wurde. Der useRef<number>(42) -Fall funktioniert also bereits korrekt. Danke für die Klarstellung!

Was müssen wir mit createRef machen? Im Moment gibt es ein unveränderliches RefObject<T> zurück, was Probleme in unserer Codebasis verursacht, da wir sie gerne weitergeben und auf die gleiche Weise wie (veränderliche) useRef Ref-Objekte verwenden möchten. Gibt es eine Möglichkeit, die createRef -Eingabe zu optimieren, damit sie veränderliche Referenzobjekte bilden kann?

(Und das Attribut ref ist als Typ Ref<T> definiert, der RefObject<T> enthält, wodurch alles unveränderlich wird. Dies ist ein großes Problem für uns: selbst wenn wir eine Veränderliche erhalten ref von useRef , können wir die Tatsache nicht nutzen, dass es durch einen forwardRef Aufruf unveränderlich ist.)

Hmm ... vielleicht könnten wir den gleichen Trick anwenden, nur weil er keine Argumente hat (und immer mit null beginnt), würde er einen bedingten Typ benötigen.

: null extends T ? MutableRefObject<T> : RefObject<T> sollte die gleiche Logik verwenden. Wenn Sie sagen, dass Sie Nullen einfügen möchten, ist es veränderlich, wenn Sie dies nicht tun, ist es in der aktuellen Bedeutung immer noch unveränderlich.

Das ist eine sehr schöne Idee. Da createRef keinen Parameter benötigt, muss es vermutlich immer eine | null Option enthalten (im Gegensatz zu useRef ), also müsste man wahrscheinlich MutableRefObject<T | null> sagen?

Ich habe es nicht geschafft, beides in TS zum Laufen zu bringen. Hier ist der damit konfigurierte TS-Spielplatz:
https://tinyurl.com/y75c32y3

Der Typ wird immer als MutableRefObject erkannt.

Was können wir Ihrer Meinung nach mit forwardRef machen? Es deklariert ref als Ref<T> , was die Möglichkeit eines MutableRefObject nicht einschließt.

(Unser Anwendungsfall, der Schwierigkeiten verursacht, ist der einer mergeRefs -Funktion, die ein Array von Refs nimmt, die entweder funktionale Refs oder Ref-Objekte sein können, und eine einzelne kombinierte Ref [eine funktionale Ref] erstellt, die übergeben werden kann Diese kombinierte Referenz liefert dann alle eingehenden referenzierten Elemente an alle bereitgestellten Referenzen, entweder durch Aufrufen, wenn es sich um funktionale Referenzen handelt, oder durch Setzen von .current , wenn es sich um Referenzobjekte handelt das unveränderliche RefObject<T> und die fehlende Einbeziehung von MutableRefObject<T> in Ref<T> erschweren das. Sollte ich ein separates Problem für forwardRef und Ref aufwerfen?Schwierigkeiten?)

Wir ändern den Typ für useRef aus den oben aufgeführten Gründen nicht (obwohl createRef noch diskutiert wird). Haben Sie Fragen zur Begründung?

Sollte ich ein separates Problem für die in https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065#issuecomment -457650501 behandelten Elemente ansprechen?

Ja, lass es uns trennen.

Wenn Sie ein änderbares Referenzobjekt wünschen, das mit einem Nullwert beginnt, stellen Sie sicher, dass Sie dem generischen Argument auch | null geben. Das macht es veränderlich, weil Sie es "besitzen" und nicht reagieren.

Danke dafür! Das Hinzufügen von null zum useRef Generikum hat es für mich gelöst.

Vor

const ref = useRef<SomeType>(null) 

// Later error: Cannot assign to 'current' because it is a constant or a read-only property.

Nach

const ref = useRef<SomeType | null>(null)

Wurde ein weiterer Kommentar zu forwardRef-Komponenten erstellt? Im Wesentlichen können Sie eine Referenz nicht weiterleiten und ihren aktuellen Wert direkt ändern, was meiner Meinung nach Teil des Weiterleitens der Referenz ist.

Ich habe folgenden Hook erstellt:

export const useCombinedRefs = <T>(...refs: Ref<T>[]) =>
    useCallback(
        (element: T) =>
            refs.forEach(ref => {
                if (!ref) {
                    return;
                }

                if (typeof ref === 'function') {
                    ref(element);
                } else {
                    ref.current = element; // this line produces error
                }
            }),
        refs,
    );

Und ich erhalte eine Fehlermeldung: „Kann ‚aktuell‘ nicht zuweisen, da es sich um eine schreibgeschützte Eigenschaft handelt.“
Gibt es eine Möglichkeit, es zu lösen, ohne current in beschreibbar zu ändern?

Dafür musst du cheaten.

(ref.current as React.MutableRefObject<T> ).current = element;

Ja, das ist ein bisschen ungesund, aber es ist der einzige Fall, an den ich denken kann, wo diese Art von Zuweisung absichtlich und kein Unfall ist – Sie replizieren ein internes Verhalten von React und müssen daher die Regeln brechen.

Wir könnten createRef so modifizieren, dass es dieselbe Überladungslogik hat, aber da es kein Argument gibt, müsste es mit einer bedingten Typrückgabe erfolgen. Wenn Sie | null einschließen, würde es MutableRefObject zurückgeben, wenn Sie es nicht tun, wäre es (unveränderlich) RefObject .

Danke vielmals

(ref.current as React.MutableRefObject<T>).current = element;

sollte sein:

(ref as React.MutableRefObject<T>).current = element;

Sie replizieren ein internes Verhalten von React

Bedeutet das, dass die Dokumente hier veraltet sind? Weil sie dies eindeutig als beabsichtigten Arbeitsablauf beschreiben, nicht als internes Verhalten.

Die Dokumentation bezieht sich in diesem Fall auf eine Referenz, die Sie besitzen, aber für Referenzen, die Sie als ref -Attribut an ein HTML-Element übergeben, sollten sie _für Sie_ schreibgeschützt sein.

function Component() {
  // same API, different type semantics
  const countRef = useRef<number>(0); // not readonly
  const divRef = useRef<HTMLElement>(null); // readonly

  return <button ref={divRef} onClick={() => countRef.current++}>Click me</button>
}

Mein Fehler, hätte weiter nach oben scrollen sollen. Ich habe einen Fehler über readonly für einen Ref erhalten, den ich besessen habe, und davon ausgegangen, dass dies derselbe Fall war. (Ich benutze jetzt einen anderen Workflow und kann den Fehler leider nicht mehr reproduzieren...)

Trotzdem vielen Dank für die Erklärung!

Ich bin mir nicht sicher, ob der createRef -Teil des Themas verschoben wurde - aber ich werde auch hier posten.

Ich verwende eine beliebte Navigationsbibliothek für React Native (React Navigation). In seiner Dokumentation nennt es üblicherweise createRef und mutiert dann die Referenz. Ich bin mir sicher, dass dies daran liegt, dass React sie nicht verwaltet (es gibt kein DOM).

Sollte der Typ für React Native anders sein?

Siehe: https://reactnavigation.org/docs/navigating-without-navigation-prop

@sylvanar
Ich hatte dieses Problem auch beim Initialisieren der Navigation. Haben Sie eine Lösung gefunden?

Zusammenfassend, was ich gerade gelesen habe: Die einzige Möglichkeit, einem von createRef<T> erstellten Verweis einen Wert zuzuweisen, besteht darin, ihn jedes Mal umzuwandeln, wenn Sie ihn verwenden? Was ist die Überlegung dahinter? In diesem Fall verhindert readonly praktisch, dass überhaupt ein Wert für die Referenz gesetzt wird, wodurch der gesamte Zweck der Funktion zunichte gemacht wird.

TLDR: Wenn Ihr Anfangswert null ist (Details: oder etwas anderes außerhalb des Typparameters), dann fügen Sie Ihrem Typparameter | null hinzu, und das sollte .current fähig machen wie gewohnt zuzuordnen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen