Sekarang boleh saja untuk menetapkan ref.current
, lihat contoh: https://reactjs.org/docs/hooks-faq.html#is -there-something-like-instance-variables
Mencoba menetapkan nilai untuk itu memberikan kesalahan: Cannot assign to 'current' because it is a constant or a read-only property.
@types/react
dan mengalami masalah.Definitions by:
di index.d.ts
) agar mereka dapat menanggapi.PR akan dihargai! Ini harus menjadi perubahan satu baris yang sangat mudah 😊
@ferdaber ,
Hai, saya suka memilih masalah ini. Karena saya baru mengenal TypeScript (hanya seminggu) dan ini akan menjadi kontribusi pertama saya untuk open source.
Saya telah melalui node_modules/@types/react/index.d.ts dan ada kode berikut pada baris no 61 yang menyatakan saat ini sebagai hanya-baca.
interface RefObject<T> {
readonly current: T | null;
}
jika ini masalahnya, maka saya bisa menyelesaikannya. dapatkah Anda membimbing untuk PR pertama saya?
Terima kasih atas waktu Anda :)
Ya, itu hanya menghapus pengubah readonly
pada baris itu.
React.useRef
mengembalikan MutableRefObject
yang current
propertinya bukan readonly
. Saya kira pertanyaannya adalah apakah tipe objek ref harus disatukan.
Mereka harus disatukan, runtime React tidak memiliki batasan pada properti current
yang dibuat oleh React.createRef()
, objek itu sendiri disegel tetapi tidak dibekukan.
Ini bukan. Ini sengaja dibiarkan hanya-baca untuk memastikan penggunaan yang benar, meskipun tidak dibekukan. Referensi yang diinisialisasi dengan null tanpa secara khusus menunjukkan bahwa Anda ingin dapat menetapkan null untuk itu ditafsirkan sebagai referensi yang ingin Anda kelola oleh React -- yaitu React "memiliki" arus dan Anda hanya melihatnya.
Jika Anda menginginkan objek ref yang dapat diubah yang dimulai dengan nilai nol, pastikan juga memberikan | null
ke argumen generik. Itu akan membuatnya bisa berubah, karena Anda "memilikinya" dan bukan Bereaksi.
Mungkin ini lebih mudah untuk saya pahami karena saya telah sering bekerja dengan bahasa berbasis pointer sebelumnya dan kepemilikan _sangat_ penting di dalamnya. Dan itulah ref, pointer. .current
sedang mendereferensi pointer.
Itu adil, saya telah menggunakan React.createRef()
sebagai fungsi pembantu untuk hanya membuat pointer, karena React tidak akan mengelolanya kecuali jika diteruskan sebagai prop ref
, tetapi Anda bisa juga baik membuat objek pointer sederhana dengan struktur yang sama.
Kita dapat memodifikasi createRef
untuk memiliki logika kelebihan yang sama, tetapi karena tidak ada argumen, itu harus dengan tipe pengembalian bersyarat. Jika Anda memasukkan | null
itu akan mengembalikan MutableRefObject
, jika tidak, itu akan (tidak berubah) RefObject
.
Apakah itu akan berhasil bagi mereka yang tidak mengaktifkan strictNullTypes
?
🤔.
Haruskah kita benar-benar mempermudah penulisan kode yang salah untuk kode yang _melakukan_ strictNullTypes
diaktifkan?
Ketika saya membuat masalah ini, saya tidak menyadari bahwa sudah ada kelebihan | null
ini. Ini bukan pola yang sangat umum jadi saya tidak mengharapkan sesuatu seperti itu ada. Saya tidak tahu bagaimana saya melewatkannya dalam dokumentasi, itu didokumentasikan dan dijelaskan dengan sangat baik. Saya baik-baik saja dengan menutup ini sebagai non-masalah kecuali jika Anda ingin menyatukan useRef
dan createRef
.
Karena masalah itu sendiri didasarkan pada useRef
dan bukan createRef
, saya juga setuju dengan menutup ini karena sangat sedikit orang di luar sana yang secara langsung mengubah nilai penunjuk dereferensi di createRef
.
Saya bertanya-tanya apakah pola kelebihan beban ini akan berjalan dengan baik, melihat dokumentasi kait kita melihat beberapa penggunaan useRef
dengan nilai default, dalam hal ini nilai .current
mungkin tidak akan pernah null tetapi dapat mengganggu pengguna untuk terus-menerus menggunakan operator pernyataan non-null untuk melewatinya.
Apakah lebih masuk akal jika nilai yang dikembalikan bisa berubah jika nilai awal diberikan, tetapi tidak berubah jika dihilangkan? Jika nilai awal diberikan, maka referensi mungkin tidak akan diteruskan ke komponen melalui ref
, dan digunakan lebih seperti variabel instan. Di sisi lain, saat membuat referensi semata-mata untuk diteruskan ke komponen, maka nilai awal mungkin tidak akan diberikan.
Itu semacam apa yang saya pikirkan. @Kovensky apa pendapat Anda?
@ferdaber useRef
seperti yang didefinisikan sekarang akan selalu berubah _dan_ tidak dapat dibatalkan kecuali jika Anda secara eksplisit memberikan argumen umum yang tidak menyertakan | null
dan nilai awal null
.
Referensi yang diteruskan ke komponen harus diinisialisasi dengan null
karena itulah yang React set referensi saat dirilis (misalnya saat melepas komponen yang dipasang secara kondisional). useRef
tanpa memberikan nilai akan menyebabkannya memulai undefined
, jadi sekarang Anda juga harus menambahkan | undefined
ke sesuatu yang hanya perlu diperhatikan | null
.
Masalah dalam dokumentasi React dan menggunakan useRef
tanpa nilai awal adalah bahwa tim React tampaknya tidak terlalu peduli dengan perbedaan antara null
dan undefined
. Itu mungkin hal Flow.
Bagaimanapun, cara saya mendefinisikan useRef
sangat cocok dengan kasus penggunaan @bschlenk , hanya saja null
adalah nilai "dihilangkan", untuk alasan yang saya sebutkan di atas.
Ah, melihat lebih dekat menunjukkan itu, dan menarik bahwa ini diinisialisasi sebagai undefined
di sumber React tanpa parameter. Keren jadi semuanya bagus, kedengarannya seperti 👍
Saya pikir tidak apa-apa, hanya sedikit aneh bahwa apakah Anda memasukkan | null
, jenis current
masih bisa nol, hanya mutabilitas yang berubah. Juga, dokumen React selalu secara eksplisit memberikan null, jadi apakah valid untuk menghilangkan initialValue?
Umumnya tidak, dan setidaknya di masa lalu ketika saya menunjukkan bahwa dalam beberapa dokumentasi tim React mengatakan bahwa "bahkan jika berhasil" itu tidak dimaksudkan untuk dihilangkan.
Itu sebabnya argumen pertama ke createContext
diperlukan, misalnya, bahkan jika Anda ingin konteks dimulai dengan undefined
. Anda harus benar-benar lulus undefined
.
Sepertinya dalam beberapa penggunaan tidak menggunakan parameter:
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 itu karena mereka tidak peduli dengan tipe di sana. Apa yang seharusnya menjadi tipe current
dalam useRef()
(tanpa argumen)? undefined
? any
? Apa pun itu, bahkan jika Anda memberikan argumen umum, itu harus |
ed dengan undefined
. _And_ jika itu adalah referensi yang Anda gunakan sebagai referensi elemen reaksi (bukan hanya sebagai penyimpanan lokal utas) maka Anda juga harus | null
karena React dapat menulis null
di sana.
Sekarang Anda menemukan diri Anda current
menjadi T | null | undefined
bukan hanya 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)
Ya, DX ini berfungsi untuk saya, dan dokumentasinya adalah A++ jadi saya rasa kebanyakan orang tidak akan bingung karenanya. Terima kasih untuk elaborasi ini! Sejujurnya Anda hanya perlu menautkan masalah ini di typedoc :)
Mungkin ada kasus untuk mendukung useRef<T>()
dan membuatnya berperilaku sama dengan useRef<T | undefined>(undefined)
; tetapi referensi apa pun yang Anda buat dengan itu masih tidak dapat digunakan sebagai referensi elemen, seperti penyimpanan lokal utas.
Masalahnya adalah... apa yang terjadi jika Anda _tidak_ memberikan argumen umum, yang mana yang diperbolehkan? TypeScript hanya akan menyimpulkan {}
. Jenis default yang benar adalah unknown
tetapi kami tidak dapat menggunakannya.
Saya mendapatkan kesalahan ini dengan kode berikut:
~~~js
// ...
biarkan intervalRef = useRef
// ...
useEffect() => {
const interval = setInterval() => { /* lakukan sesuatu */}, 1000);
intervalRef.current = interval; // Di baris ini saya mendapatkan kesalahan
return () => {
clearInterval(intervalRef.current);
}
})
// ...
~Dan ketika saya menghapus readonly
di sini berfungsi:~ js
antarmuka RefObject
hanya baca saat ini: T | batal;
}
~~~
Saya baru dengan reack hooks dan TypeScript (hanya mencobanya bersama) sehingga kode saya bisa salah
Secara default jika Anda membuat referensi dengan nilai default null
dan menentukan parameter generiknya, Anda menandakan niat Anda untuk membuat React "memiliki" referensi. Jika Anda ingin dapat mengubah referensi yang Anda miliki, Anda harus mendeklarasikannya seperti ini:
const intervalRef= useRef<NodeJS.Timeout | null>(null) // <-- the generic type is NodeJS.Timeout | null
@ferdaber Terima kasih untuk itu!
Mengambil ini lebih jauh dan melihat tipe pengembalian current
, haruskah itu T
daripada T | null
? Dengan munculnya kait, kami _tidak selalu_ memiliki kasus bahwa semua referensi mungkin null
, terutama dalam kasus yang sering terjadi di mana useRef
dipanggil dengan penginisialisasi non-null.
Melanjutkan dari daftar contoh yang sangat baik di https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065#issuecomment -446660394, jika saya menulis:
const numericRef = useRef<number>(42);
apa yang seharusnya menjadi tipe numericRef.current
? Tidak ada _need_ untuk itu menjadi number | null
.
Jika kita mendefinisikan jenis dan fungsinya sebagai berikut:
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>;
yang akan menghasilkan penggunaan dan jenis berikut:
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
Apakah ada yang salah?
Untuk jawaban "apa jenis useRef
yang tidak berparameter?", Jawabannya adalah bahwa panggilan itu (meskipun dokumentasinya) salah, menurut tim React.
Saya menambahkan kelebihan dengan |null _specifically_ sebagai kelebihan kenyamanan untuk DOM/referensi komponen, karena mereka selalu memulai nol, mereka selalu disetel ulang ke nol saat dilepas, dan Anda tidak pernah menetapkan ulang sendiri saat ini, hanya Bereaksi.
Readonly ada lebih banyak untuk menjaga terhadap kesalahan logika daripada representasi objek beku JavaScript yang benar/properti getter-only.
Anda hanya dapat membuatnya secara tidak sengaja ketika Anda berdua memberikan argumen umum yang mengatakan bahwa Anda tidak menerima nol saat menginisialisasi nilai ke nol. Kasus lain akan berubah.
Ah, ya, sekarang saya mengerti bahwa MutableRefObject<T>
sudah menghapus | null
case, dibandingkan dengan RefObject<T>
. Jadi kasing useRef<number>(42)
sudah berfungsi dengan benar. Terimakasih atas klarifikasinya!
Apa yang perlu kita lakukan dengan createRef
? Saat ini ia mengembalikan RefObject<T>
yang tidak dapat diubah, yang menyebabkan masalah dalam basis kode kami karena kami ingin menyebarkannya dan menggunakannya dengan cara yang sama seperti objek ref useRef
(dapat berubah). Apakah ada cara kita dapat mengubah pengetikan createRef
untuk memungkinkannya membentuk objek ref yang dapat diubah?
(Dan atribut ref
didefinisikan bertipe Ref<T>
yang menyertakan RefObject<T>
, sehingga membuat semuanya tidak dapat diubah. Ini adalah masalah besar bagi kami: bahkan jika kami mendapatkan ref dari useRef
, kami tidak dapat memanfaatkan fakta bahwa itu tidak dapat diubah melalui panggilan forwardRef
.)
Hmm... mungkin kita bisa menggunakan trik yang sama, hanya karena tidak memiliki argumen (dan selalu dimulai dengan nol), itu membutuhkan tipe kondisional.
: null extends T ? MutableRefObject<T> : RefObject<T>
harus melakukan dan menggunakan logika yang sama. Jika Anda mengatakan Anda ingin memasukkan nol di dalamnya, itu bisa berubah, jika tidak, itu masih tidak berubah dalam arti saat ini.
Itu ide yang sangat bagus. Karena createRef
tidak mengambil parameter, itu mungkin selalu perlu menyertakan opsi | null
(tidak seperti useRef
), sehingga mungkin perlu dikatakan MutableRefObject<T | null>
?
Saya belum bisa mendapatkan keduanya untuk bekerja di TS. Inilah taman bermain TS yang dikonfigurasi dengannya:
https://tinyurl.com/y75c32y3
Jenisnya selalu terdeteksi sebagai MutableRefObject
.
Menurut Anda apa yang bisa kita lakukan dengan forwardRef
? Itu mendeklarasikan ref
sebagai Ref<T>
, yang tidak termasuk kemungkinan MutableRefObject
.
(Kasus penggunaan kami yang menyebabkan kesulitan adalah fungsi mergeRefs
yang mengambil larik referensi, yang dapat berupa referensi fungsional atau objek ref, dan membuat satu referensi gabungan [a ref fungsional] yang dapat diteruskan ke komponen. Ref gabungan itu kemudian mengirimkan elemen referensi yang masuk ke semua referensi yang disediakan, baik dengan memanggil mereka jika referensi fungsional, atau dengan menyetel .current
jika mereka adalah objek ref. Tetapi keberadaan RefObject<T>
yang tidak dapat diubah dan kurangnya penyertaan MutableRefObject<T> in Ref<T>
membuatnya sulit. Haruskah saya mengangkat masalah terpisah untuk forwardRef dan Ref
Kami tidak mengubah jenis untuk useRef
karena alasan yang dijelaskan di atas (meskipun createRef
masih dalam diskusi), apakah Anda memiliki pertanyaan tentang alasannya?
Haruskah saya mengangkat masalah terpisah untuk item yang tercakup dalam https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065#issuecomment -457650501?
Ya, mari kita pisahkan.
Jika Anda menginginkan objek ref yang dapat diubah yang dimulai dengan nilai nol, pastikan juga memberikan
| null
ke argumen generik. Itu akan membuatnya bisa berubah, karena Anda "memilikinya" dan bukan Bereaksi.
Terima kasih untuk ini! Menambahkan null ke generik useRef
menyelesaikannya untuk saya.
Sebelum
const ref = useRef<SomeType>(null)
// Later error: Cannot assign to 'current' because it is a constant or a read-only property.
Setelah
const ref = useRef<SomeType | null>(null)
Apakah komentar lain tentang komponen forwardRef dibuat? Pada dasarnya, Anda tidak dapat meneruskan referensi dan mengubah nilainya saat ini secara langsung, yang menurut saya merupakan bagian dari tujuan meneruskan referensi.
Saya membuat kait berikut:
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,
);
Dan saya mendapatkan kesalahan: "Tidak dapat menetapkan ke 'saat ini' karena ini adalah properti hanya-baca."
Apakah ada cara untuk menyelesaikannya tanpa mengubah current
menjadi dapat ditulisi?
Untuk itu, Anda harus menipu.
(ref.current as React.MutableRefObject<T> ).current = element;
Ya, ini agak tidak masuk akal, tapi itu satu-satunya kasus yang dapat saya pikirkan di mana penugasan semacam ini disengaja dan bukan kebetulan -- Anda mereplikasi perilaku internal React dan dengan demikian harus melanggar aturan.
Kita dapat memodifikasi
createRef
untuk memiliki logika kelebihan yang sama, tetapi karena tidak ada argumen, itu harus dengan tipe pengembalian bersyarat. Jika Anda memasukkan| null
itu akan mengembalikanMutableRefObject
, jika tidak, itu akan (tidak berubah)RefObject
.
Terima kasih banyak
(ref.current as React.MutableRefObject<T>).current = element;
seharusnya:
(ref as React.MutableRefObject<T>).current = element;
Anda mereplikasi perilaku internal React
Apakah itu berarti dokumen di sini sudah usang? Karena mereka dengan jelas menggambarkan ini sebagai alur kerja yang dimaksudkan, bukan perilaku internal.
Dokumen dalam kasus tersebut mengacu pada referensi yang Anda miliki, tetapi untuk referensi yang Anda berikan sebagai atribut ref
ke elemen HTML, maka referensi tersebut harus hanya dapat dibaca _untuk Anda_.
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>
}
Buruk saya, seharusnya menggulir lebih jauh. Mendapat kesalahan tentang readonly
untuk referensi yang saya miliki dan berasumsi bahwa itu adalah kasus yang sama. (Saya menggunakan alur kerja yang berbeda sekarang dan tidak dapat mereproduksi kesalahan lagi, sayangnya...)
Bagaimanapun, terima kasih banyak atas penjelasannya!
Saya tidak jelas apakah bagian createRef
dari topik dipindahkan - tetapi saya akan memposting di sini juga.
Saya menggunakan perpustakaan navigasi populer untuk React Native (React Navigation). Dalam dokumentasinya biasanya memanggil createRef
dan kemudian memutasi referensi. Saya yakin ini karena React tidak mengelolanya (tidak ada DOM).
Apakah tipe React Native harus berbeda?
Lihat: https://reactnavigation.org/docs/navigating-without-navigation-prop
@sylvanaar
Saya juga menghadapi masalah ini ketika menginisialisasi navigasi, sudahkah Anda menemukan solusi?
Meringkas apa yang baru saja saya baca: Satu-satunya cara untuk menetapkan nilai ke referensi yang dibuat oleh createRef<T>
adalah dengan melemparkannya setiap kali Anda menggunakannya? Apa alasan di balik itu? Dalam hal ini readonly
praktis mencegah pengaturan nilai ke referensi sama sekali, oleh karena itu mengalahkan seluruh tujuan fungsi.
TLDR: Jika nilai awal Anda adalah null
(detail: atau sesuatu yang lain di luar parameter tipe), kemudian tambahkan | null
ke parameter tipe Anda, dan itu akan membuat .current
dapat untuk ditugaskan seperti biasa.
Komentar yang paling membantu
Ini bukan. Ini sengaja dibiarkan hanya-baca untuk memastikan penggunaan yang benar, meskipun tidak dibekukan. Referensi yang diinisialisasi dengan null tanpa secara khusus menunjukkan bahwa Anda ingin dapat menetapkan null untuk itu ditafsirkan sebagai referensi yang ingin Anda kelola oleh React -- yaitu React "memiliki" arus dan Anda hanya melihatnya.
Jika Anda menginginkan objek ref yang dapat diubah yang dimulai dengan nilai nol, pastikan juga memberikan
| null
ke argumen generik. Itu akan membuatnya bisa berubah, karena Anda "memilikinya" dan bukan Bereaksi.Mungkin ini lebih mudah untuk saya pahami karena saya telah sering bekerja dengan bahasa berbasis pointer sebelumnya dan kepemilikan _sangat_ penting di dalamnya. Dan itulah ref, pointer.
.current
sedang mendereferensi pointer.