Definitelytyped: [@types/react] RefObject.current ne devrait plus ĂȘtre en lecture seule

CrĂ©Ă© le 5 dĂ©c. 2018  Â·  48Commentaires  Â·  Source: DefinitelyTyped/DefinitelyTyped

Il est maintenant correct d'attribuer à ref.current , voir l'exemple : https://reactjs.org/docs/hooks-faq.html#is -there-something-like-instance-variables

Essayer de lui attribuer une valeur donne l'erreur : Cannot assign to 'current' because it is a constant or a read-only property.

  • [x] J'ai essayĂ© d'utiliser le package @types/react et j'ai eu des problĂšmes.
  • [x] J'ai essayĂ© d'utiliser la derniĂšre version stable de tsc. https://www.npmjs.com/package/typescript
  • [x] J'ai une question inappropriĂ©e pour StackOverflow . (Veuillez y poser toutes les questions appropriĂ©es).
  • [x] [Mention](https://github.com/blog/821-mention-somebody-they-re-notified) les auteurs (voir Definitions by: dans index.d.ts ) afin qu'ils puissent rĂ©pondre.

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

Commentaire le plus utile

Ce n'est pas. Il est intentionnellement laissĂ© en lecture seule pour garantir une utilisation correcte, mĂȘme s'il n'est pas gelĂ©. Les rĂ©fĂ©rences initialisĂ©es avec null sans indiquer spĂ©cifiquement que vous souhaitez pouvoir lui attribuer null sont interprĂ©tĂ©es comme des rĂ©fĂ©rences que vous souhaitez gĂ©rer par React - c'est-Ă -dire que React "possĂšde" le courant et que vous le visualisez simplement.

Si vous voulez un objet ref mutable qui commence par une valeur nulle, assurez-vous également de donner | null à l'argument générique. Cela le rendra modifiable, car vous le "possédez" et non React.

C'est peut-ĂȘtre plus facile Ă  comprendre pour moi car j'ai souvent travaillĂ© avec des langages basĂ©s sur des pointeurs auparavant et la propriĂ©tĂ© est _trĂšs_ importante en eux. Et c'est ce que sont les refs, un pointeur. .current dĂ©rĂ©fĂ©rence le pointeur.

Tous les 48 commentaires

Un PR serait apprĂ©ciĂ© ! Cela devrait ĂȘtre un changement de ligne trĂšs facile 😊

@ferdaber ,
Salut, j'aime choisir ce problĂšme. Comme je suis novice en matiĂšre de dactylographie (juste une semaine) et ce sera ma premiĂšre contribution Ă  l'open source.

J'ai parcouru node_modules/@types/react/index.d.ts et il y a le code suivant à la ligne n° 61 qui déclare courant en lecture seule.

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

si tel est le problÚme, alors je peux le résoudre. pouvez-vous guider pour mon premier PR?
Merci pour votre temps :)

Oui, il supprime simplement le modificateur readonly sur cette ligne.

React.useRef renvoie un MutableRefObject dont la propriĂ©tĂ© current n'est pas readonly . Je suppose que la question est de savoir si les types d'objets ref doivent ĂȘtre unifiĂ©s.

Ils doivent ĂȘtre unifiĂ©s, le runtime React n'a aucune limitation sur la propriĂ©tĂ© current crĂ©Ă©e par React.createRef() , l'objet lui-mĂȘme est scellĂ© mais pas gelĂ©.

Ce n'est pas. Il est intentionnellement laissĂ© en lecture seule pour garantir une utilisation correcte, mĂȘme s'il n'est pas gelĂ©. Les rĂ©fĂ©rences initialisĂ©es avec null sans indiquer spĂ©cifiquement que vous souhaitez pouvoir lui attribuer null sont interprĂ©tĂ©es comme des rĂ©fĂ©rences que vous souhaitez gĂ©rer par React - c'est-Ă -dire que React "possĂšde" le courant et que vous le visualisez simplement.

Si vous voulez un objet ref mutable qui commence par une valeur nulle, assurez-vous également de donner | null à l'argument générique. Cela le rendra modifiable, car vous le "possédez" et non React.

C'est peut-ĂȘtre plus facile Ă  comprendre pour moi car j'ai souvent travaillĂ© avec des langages basĂ©s sur des pointeurs auparavant et la propriĂ©tĂ© est _trĂšs_ importante en eux. Et c'est ce que sont les refs, un pointeur. .current dĂ©rĂ©fĂ©rence le pointeur.

C'est juste, j'ai utilisĂ© React.createRef() comme fonction d'assistance pour crĂ©er simplement un pointeur, car React ne le gĂ©rera pas Ă  moins qu'il ne soit passĂ© en tant qu'accessoire ref , mais vous pouvez tout aussi bien bien crĂ©er un objet pointeur simple avec la mĂȘme structure.

Nous pourrions modifier createRef pour avoir la mĂȘme logique de surcharge, mais comme il n'y a pas d'argument, cela devrait ĂȘtre avec un retour de type conditionnel. Si vous incluez | null , cela renverrait MutableRefObject , sinon ce serait (immuable) RefObject .

Cela fonctionnerait-il pour ceux qui n'ont pas activé strictNullTypes ?

đŸ€”

Devrions-nous vraiment faciliter l'écriture de code incorrect pour ceux qui _do_ ont activé strictNullTypes ?

Lorsque j'ai créé ce problÚme, je ne savais pas qu'il y avait déjà cette surcharge | null . Ce n'est pas un modÚle trÚs courant, donc je ne m'attendais pas à ce que quelque chose comme ça existe. Je ne sais pas comment je l'ai raté dans la documentation, c'est trÚs bien documenté et expliqué. Je suis d'accord pour fermer cela comme un non-problÚme, sauf si vous souhaitez unifier useRef et createRef .

Étant donnĂ© que le problĂšme lui-mĂȘme Ă©tait basĂ© sur useRef et non createRef , je suis Ă©galement d'accord pour fermer ceci car trĂšs peu de gens modifient directement la valeur du pointeur dĂ©rĂ©fĂ©rencĂ© dans createRef .

Je me demande cependant si ce modĂšle de surcharge se dĂ©roulera bien, en regardant la documentation des crochets, nous voyons quelques utilisations de useRef avec une valeur par dĂ©faut, auquel cas la valeur de .current peut ne jamais ĂȘtre null mais il peut ĂȘtre assez ennuyeux pour les utilisateurs d'utiliser constamment l'opĂ©rateur d'assertion non nul pour le dĂ©passer.

Serait-il plus logique que la valeur de retour soit modifiable si une valeur initiale est donnée, mais immuable si elle est omise ? Si une valeur initiale est donnée, la référence ne sera probablement pas transmise à un composant via ref , et utilisée davantage comme une variable d'instance. D'autre part, lors de la création d'une référence uniquement à transmettre à un composant, une valeur initiale ne sera probablement pas fournie.

C'est un peu ce que je pensais. @Kovensky, qu'en pensez-vous ?

@ferdaber useRef tel qu'il est défini en ce moment sera toujours mutable _et_ non nullable à moins que vous ne lui donniez explicitement un argument générique qui n'inclut pas | null et une valeur initiale de null .

Les rĂ©fĂ©rences transmises Ă  un composant doivent ĂȘtre initialisĂ©es avec null car c'est ce Ă  quoi React dĂ©finit les rĂ©fĂ©rences lorsqu'elles sont publiĂ©es (par exemple lors du dĂ©montage d'un composant montĂ© de maniĂšre conditionnelle). useRef sans passer une valeur le ferait commencer undefined la place, donc maintenant vous devriez aussi ajouter | undefined Ă  quelque chose qui n'avait qu'Ă  se soucier de | null .

Le problĂšme dans la documentation de React et l'utilisation useRef sans valeur initiale est que l'Ă©quipe React ne semble pas trop se soucier de la diffĂ©rence entre null et undefined . Cela pourrait ĂȘtre une chose Flow.


Quoi qu'il en soit, la façon dont j'ai défini useRef correspond exactement au cas d'utilisation de @bschlenk , sauf que null est la valeur "omise", pour les raisons que j'ai mentionnées ci-dessus.

Ah, regarder de plus prĂšs montre cela, et il est intĂ©ressant qu'il soit initialisĂ© comme undefined dans la source React sans paramĂštre. Cool donc tout est pĂȘche, on dirait 👍

Je pense que c'est bien comme ça, juste un peu Ă©trange que si vous incluez le | null , le type de current peut toujours ĂȘtre nul, juste la mutabilitĂ© a changĂ©. De plus, les documents React passent toujours explicitement null, est-il donc mĂȘme valide d'omettre le initialValue ?

Ce n'est gĂ©nĂ©ralement pas le cas, et au moins dans le passĂ©, lorsque je l'ai soulignĂ© dans une documentation, l'Ă©quipe React a dĂ©clarĂ© que "mĂȘme si cela fonctionne", il n'est pas destinĂ© Ă  ĂȘtre omis.

C'est pourquoi le premier argument de createContext est requis, par exemple, mĂȘme si vous voulez qu'un contexte commence par undefined . Vous devez rĂ©ellement passer undefined .

@ferdaber c'est parce qu'ils ne se soucient pas des types lĂ -bas. Quel devrait ĂȘtre le type de current dans un useRef() (sans arguments) ? undefined ? any ? Quoi qu'il en soit, mĂȘme si vous donnez un argument gĂ©nĂ©rique, il devra ĂȘtre | ed avec undefined . _Et_ s'il s'agit d'une rĂ©fĂ©rence que vous utilisez comme rĂ©fĂ©rence d'Ă©lĂ©ment de rĂ©action (pas seulement comme stockage local de thread), vous devez Ă©galement | null car React peut y Ă©crire un null .

Maintenant, vous vous retrouvez current Ă©tant T | null | undefined au lieu de seulement 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)

Oui, ce DX fonctionne pour moi, et la documentation est A++, donc je ne pense pas que la plupart des gens s'y tromperont. Merci pour cette Ă©laboration ! HonnĂȘtement, vous devriez juste permalinker ce problĂšme dans le typedoc :)

Il pourrait y avoir un cas pour prendre en charge useRef<T>() et le faire se comporter de la mĂȘme maniĂšre que useRef<T | undefined>(undefined) ; mais quelle que soit la rĂ©fĂ©rence que vous crĂ©ez avec cela, elle ne peut toujours pas ĂȘtre utilisĂ©e comme rĂ©fĂ©rence d'Ă©lĂ©ment, tout comme le stockage local de thread.

Le problÚme est... que se passe-t-il si vous ne donnez pas d'argument générique, ce qui est autorisé ? TypeScript déduira simplement {} . Le type par défaut correct est unknown mais nous ne pouvons pas l'utiliser.

J'obtiens cette erreur avec le code suivant :

~~~js
// ...
soit intervalleRef = useRef(nul); // également essayé avec const au lieu de let
// ...
useEffet( () => {
const interval = setInterval( () => { /* faire quelque chose */}, 1000);
intervalleRef.current = intervalle ; // Dans cette ligne, j'obtiens l'erreur

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

})
// ...
~Et quand je supprime le readonly ici, cela fonctionne :~ js
interface RefObject{
courant en lecture seule : T | nul;
}
~~~

Je suis nouveau avec les crochets de reack et le tapuscrit (juste en les essayant ensemble) donc mon code pourrait ĂȘtre faux

Par défaut, si vous créez une référence avec une valeur par défaut null et que vous spécifiez son paramÚtre générique, vous signalez votre intention que React "possÚde" la référence. Si vous voulez pouvoir muter une référence que vous possédez, vous devez la déclarer comme suit :

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

@ferdaber Merci pour ça !

En allant plus loin et en regardant le type de retour de current , devrait-il peut-ĂȘtre ĂȘtre T plutĂŽt que T | null ? Avec l'avĂšnement des crochets, nous _n'avons pas toujours_ le cas oĂč toutes les rĂ©fĂ©rences pourraient ĂȘtre null , en particulier dans le cas frĂ©quent oĂč useRef est appelĂ© avec un initialiseur non nul.

Poursuivant à partir de l'excellente liste d'exemples dans https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065#issuecomment -446660394, si j'écris :

const numericRef = useRef<number>(42);

quel devrait ĂȘtre le type de numericRef.current ? Il n'y a pas _besoin_ qu'il soit number | null .

Si nous définissons les types et les fonctions comme suit :

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

qui produirait les usages et types suivants :

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

Y a-t-il quelque chose qui ne va pas?

Pour la réponse à "quel est le type d'un useRef non paramétré ?", la réponse est que cet appel (malgré la documentation) est incorrect, selon l'équipe React.

J'ai ajoutĂ© la surcharge avec |null _spĂ©cifiquement_ comme surcharge de commoditĂ© pour les rĂ©fĂ©rences DOM/composant, car elles commencent toujours par null, elles sont toujours rĂ©initialisĂ©es Ă  null lors du dĂ©montage et vous ne rĂ©affectez jamais le courant vous-mĂȘme, uniquement React.

Le readonly est là plus pour se prémunir contre les erreurs de logique qu'une représentation d'un véritable objet gelé JavaScript / propriété getter uniquement.

Vous ne pouvez le faire que par accident lorsque vous donnez tous les deux un argument générique indiquant que vous n'acceptez pas null tout en initialisant la valeur à null de toute façon. Tout autre cas sera modifiable.

Ah, oui, je vois maintenant que MutableRefObject<T> a déjà le cas | null supprimé, par rapport à RefObject<T> . Ainsi, le cas useRef<number>(42) fonctionne déjà correctement. Merci pour la clarification!

Que devons-nous faire avec createRef ? À l'heure actuelle, il renvoie un RefObject<T> immuable, ce qui pose des problĂšmes dans notre base de code car nous aimerions les transmettre et les utiliser de la mĂȘme maniĂšre que les objets ref (mutables) useRef . Existe-t-il un moyen de modifier le typage createRef pour lui permettre de former des objets ref mutables ?

(Et l'attribut ref est dĂ©fini comme Ă©tant de type Ref<T> qui inclut RefObject<T> , rendant ainsi tout immuable. C'est un gros problĂšme pour nous : mĂȘme si nous obtenons un mutable ref sur useRef , nous ne pouvons pas utiliser le fait qu'il est immuable via un appel forwardRef .)

Hmm ... peut-ĂȘtre pourrions-nous utiliser la mĂȘme astuce, uniquement parce qu'il n'a pas d'arguments (et commence toujours par null), il aurait besoin d'un type conditionnel.

: null extends T ? MutableRefObject<T> : RefObject<T> devrait faire et utiliser la mĂȘme logique. Si vous dites que vous voulez y mettre des valeurs nulles, il est mutable, si vous ne le faites pas, il est toujours immuable dans le sens actuel.

C'est une trÚs bonne idée. Parce que createRef ne prend aucun paramÚtre, il doit probablement toujours inclure une option | null (contrairement à useRef ), donc cela devrait probablement dire MutableRefObject<T | null> ?

Je n'ai pas réussi à faire travailler l'un ou l'autre dans TS. Voici le terrain de jeu TS configuré avec :
https://tinyurl.com/y75c32y3

Le type est toujours détecté comme MutableRefObject .

Que pensez-vous que nous pouvons faire avec forwardRef ? Il déclare le ref comme étant un Ref<T> , ce qui n'inclut pas la possibilité d'un MutableRefObject .

(Notre cas d'utilisation qui cause des difficultĂ©s est celui d'une fonction mergeRefs qui prend un tableau de rĂ©fĂ©rences, qui peuvent ĂȘtre des rĂ©fĂ©rences fonctionnelles ou des objets de rĂ©fĂ©rence, et crĂ©e une seule rĂ©fĂ©rence combinĂ©e [une rĂ©fĂ©rence fonctionnelle] qui peut ĂȘtre transmise Ă  un composant. Cette rĂ©fĂ©rence combinĂ©e dĂ©livre ensuite tout Ă©lĂ©ment rĂ©fĂ©rencĂ© entrant Ă  toutes les rĂ©fĂ©rences fournies, soit en les appelant si ce sont des rĂ©fĂ©rences fonctionnelles, soit en dĂ©finissant .current s'il s'agit d'objets de rĂ©fĂ©rence. Mais l'existence de l'immuable RefObject<T> et l'absence d'inclusion de MutableRefObject<T> in Ref<T> rendent cela difficile. Dois-je soulever un problĂšme distinct pour le forwardRef et le Refdes difficultĂ©s?)

Nous ne changeons pas le type pour useRef pour les raisons détaillées ci-dessus (bien que createRef soit toujours en cours de discussion), avez-vous des questions sur la justification ?

Dois-je soulever un problÚme distinct pour les éléments couverts dans https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065#issuecomment -457650501 ?

Oui, séparons-le.

Si vous voulez un objet ref mutable qui commence par une valeur nulle, assurez-vous également de donner | null à l'argument générique. Cela le rendra modifiable, car vous le "possédez" et non React.

Merci pour cela! L'ajout de null au générique useRef l'a résolu pour moi.

Avant de

const ref = useRef<SomeType>(null) 

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

AprĂšs

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

Un autre commentaire sur les composants forwardRef a-t-il Ă©tĂ© créé ? Essentiellement, vous ne pouvez pas transmettre une rĂ©fĂ©rence et modifier directement sa valeur actuelle, ce qui, je pense, fait partie de l'intĂ©rĂȘt de transmettre la rĂ©fĂ©rence.

J'ai créé le crochet suivant :

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,
    );

Et j'obtiens une erreur : "Impossible d'attribuer à 'actuel' car il s'agit d'une propriété en lecture seule."
Existe-t-il un moyen de le résoudre sans changer current en écriture ?

Pour cela, vous allez devoir tricher.

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

Oui, c'est un peu malsain, mais c'est le seul cas auquel je puisse penser oĂč ce type d'affectation est dĂ©libĂ©rĂ© et non accidentel - vous reproduisez un comportement interne de React et devez donc enfreindre les rĂšgles.

Nous pourrions modifier createRef pour avoir la mĂȘme logique de surcharge, mais comme il n'y a pas d'argument, cela devrait ĂȘtre avec un retour de type conditionnel. Si vous incluez | null , cela renverrait MutableRefObject , sinon ce serait (immuable) RefObject .

Merci beaucoup

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

devrait ĂȘtre:

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

vous reproduisez un comportement interne de React

Cela signifie-t-il que les documents ici sont obsolÚtes ? Parce qu'ils décrivent clairement cela comme un flux de travail prévu, et non comme un comportement interne.

Dans ce cas, la documentation fait rĂ©fĂ©rence Ă  une rĂ©fĂ©rence que vous possĂ©dez, mais pour les rĂ©fĂ©rences que vous transmettez en tant qu'attribut ref Ă  un Ă©lĂ©ment HTML, elles doivent ĂȘtre en lecture seule _pour vous_.

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

Mon mauvais, aurait dĂ» faire dĂ©filer plus loin. J'ai eu une erreur Ă  propos readonly pour une rĂ©fĂ©rence que je possĂ©dais et j'ai supposĂ© que c'Ă©tait le mĂȘme cas. (J'utilise un flux de travail diffĂ©rent maintenant et je ne peux plus reproduire l'erreur, malheureusement...)

En tout cas merci beaucoup pour l'explication !

Je ne sais pas si la partie createRef du sujet a été déplacée - mais je posterai ici aussi.

J'utilise une bibliothÚque de navigation populaire pour React Native (React Navigation). Dans sa documentation, il appelle généralement createRef puis mute la réf. Je suis sûr que c'est parce que React ne les gÚre pas (il n'y a pas de DOM).

Le type de React Native devrait-il ĂȘtre diffĂ©rent ?

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

@sylvanaar
J'ai également rencontré ce problÚme lors de l'initialisation de la navigation, avez-vous trouvé une solution ?

RĂ©sumant ce que je viens de lire : la seule façon de dĂ©finir une valeur sur une rĂ©fĂ©rence crĂ©Ă©e par createRef<T> est de la lancer Ă  chaque fois que vous l'utilisez ? Quel est le raisonnement derriĂšre cela ? Dans ce cas, le readonly empĂȘche pratiquement de dĂ©finir une valeur sur la rĂ©fĂ©rence, annulant ainsi tout l'objectif de la fonction.

TLDR : Si votre valeur initiale est null (dĂ©tails : ou quelque chose d'autre en dehors du paramĂštre de type), ajoutez alors | null Ă  votre paramĂštre de type, et cela devrait rendre .current capable ĂȘtre affectĂ© Ă  comme normal.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

csharpner picture csharpner  Â·  3Commentaires

fasatrix picture fasatrix  Â·  3Commentaires

victor-guoyu picture victor-guoyu  Â·  3Commentaires

tyv picture tyv  Â·  3Commentaires

lilling picture lilling  Â·  3Commentaires