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.
@types/react
et j'ai eu des problĂšmes.Definitions by:
dans index.d.ts
) afin qu'ils puissent rĂ©pondre.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
.
On dirait que dans certaines utilisations, il n'utilise pas le paramÚtre :
https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables
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 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
// ...
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 Ref
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 renverraitMutableRefObject
, 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.
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.