Definitelytyped: [@types/react] RefObject.current seharusnya tidak lagi hanya dapat dibaca

Dibuat pada 5 Des 2018  ·  48Komentar  ·  Sumber: DefinitelyTyped/DefinitelyTyped

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.

  • [x] Saya mencoba menggunakan paket @types/react dan mengalami masalah.
  • [x] Saya mencoba menggunakan tsc versi stabil terbaru. https://www.npmjs.com/package/typescript
  • [x] Saya punya pertanyaan yang tidak pantas untuk StackOverflow . (Silakan ajukan pertanyaan yang sesuai di sana).
  • [x] [Sebutkan](https://github.com/blog/821-mention-somebody-they-re-notified) penulisnya (lihat Definitions by: di index.d.ts ) agar mereka dapat menanggapi.

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

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.

Semua 48 komentar

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(batal); // juga mencoba dengan const alih-alih let
// ...
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 Refkesulitan?)

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 mengembalikan MutableRefObject , 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.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat