componentDidMount dipanggil SEBELUM panggilan balik ref

87

Masalah

Saya mengatur react refmenggunakan definisi fungsi inline

render = () => {
    return (
        <div className="drawer" ref={drawer => this.drawerRef = drawer}>

maka componentDidMountreferensi DOM tidak disetel

componentDidMount = () => {
    // this.drawerRef is not defined

Pemahaman saya adalah refcallback harus dijalankan selama mount, namun console.logpernyataan menambahkan mengungkapkan componentDidMountdipanggil sebelum fungsi callback ref.

Contoh kode lain yang saya lihat misalnya diskusi di github ini menunjukkan asumsi yang sama, componentDidMountharus dipanggil setelahref panggilan balik apa pun yang ditentukan render, bahkan dinyatakan dalam percakapan

Jadi componentDidMount dijalankan setelah semua callback ref dijalankan?

Iya.

Saya menggunakan react 15.4.1

Sesuatu yang lain saya sudah mencoba

Untuk memverifikasi reffungsi itu dipanggil, saya mencoba mendefinisikannya di kelas seperti itu

setDrawerRef = (drawer) => {
  this.drawerRef = drawer;
}

lalu masuk render

<div className="drawer" ref={this.setDrawerRef}>

Konsol logging dalam hal ini mengungkapkan bahwa callback memang dipanggil setelahnya componentDidMount

quickshiftin
sumber
6
Saya mungkin salah, tetapi ketika Anda menggunakan fungsi panah untuk metode render, itu akan menangkap nilai dari thislingkup leksikal di luar kelas Anda. Cobalah untuk menyingkirkan sintaks fungsi panah untuk metode kelas Anda dan lihat apakah itu membantu.
Yoshi
3
@GProst Itulah sifat pertanyaan saya. Saya meletakkan console.log di kedua fungsi dan componentDidMount dijalankan pertama, panggilan balik ref kedua.
quickshiftin
3
Baru saja mengalami masalah serupa - bagi kami, pada dasarnya, kami melewatkannya di awal renderdan karenanya perlu memanfaatkan componentDidUpdate, karena componentDidMountbukan bagian dari siklus proses pembaruan . Mungkin bukan masalah Anda, tetapi menurut Anda ini layak untuk diangkat sebagai solusi potensial.
Alexander Nied
4
Sama dengan React 16. Dokumentasi dengan jelas menyatakan ref callbacks are invoked before componentDidMount or componentDidUpdate lifecycle hooks.tetapi ini tampaknya tidak benar :(
Ryan H.
1
1. deklarasi panah ref adalah: ref = {ref => { this.drawerRef = ref }}2. bahkan ref dipanggil sebelum componentDidMount; ref hanya dapat diakses setelah render awal ketika div dalam kasus Anda dirender. Jadi, Anda harus bisa mengakses ref di tingkat berikutnya, misalnya di componentWillReceiveProps menggunakan this.drawerRef3. Jika Anda mencoba mengakses sebelum pemasangan awal, Anda hanya akan mendapatkan nilai ref yang tidak ditentukan.
bh4r4

Jawaban:

156

Jawaban singkat:

React menjamin bahwa ref ditetapkan sebelum componentDidMountatau componentDidUpdatehook. Tetapi hanya untuk anak-anak yang benar-benar diberikan .

componentDidMount() {
  // can use any refs here
}

componentDidUpdate() {
  // can use any refs here
}

render() {
  // as long as those refs were rendered!
  return <div ref={/* ... */} />;
}

Perhatikan ini tidak berarti “React selalu menetapkan semua ref sebelum hook ini berjalan”.
Mari kita lihat beberapa contoh di mana wasit tidak ditetapkan.


Referensi tidak ditetapkan untuk elemen yang tidak dirender

React hanya akan memanggil callback ref untuk elemen yang sebenarnya Anda kembalikan dari render .

Artinya jika kode Anda terlihat seperti

render() {
  if (this.state.isLoading) {
    return <h1>Loading</h1>;
  }

  return <div ref={this._setRef} />;
}

dan awalnya this.state.isLoadingadalah true, Anda harus tidak mengharapkan this._setRefuntuk dipanggil sebelum componentDidMount.

Ini seharusnya masuk akal: jika render pertama Anda dikembalikan <h1>Loading</h1>, tidak ada cara yang mungkin bagi React untuk mengetahui bahwa dalam beberapa kondisi lain ia mengembalikan sesuatu yang lain yang membutuhkan ref untuk dilampirkan. Ada juga ada yang mengatur ref ke: pada <div>elemen tidak diciptakan karena render()metode mengatakan itu tidak boleh diberikan.

Jadi dengan contoh ini, hanya componentDidMountakan menembak. Namun, saat this.state.loadingberubah menjadifalse , Anda akan melihat this._setReflampiran terlebih dahulu, dan kemudian componentDidUpdateakan aktif.


Hati-hati dengan komponen lainnya

Perhatikan bahwa jika Anda meneruskan turunan dengan ref ke komponen lain, ada kemungkinan mereka melakukan sesuatu yang mencegah rendering (dan menyebabkan masalah).

Misalnya, ini:

<MyPanel>
  <div ref={this.setRef} />
</MyPanel>

tidak akan berfungsi jika MyPaneltidak disertakan props.childrendalam outputnya:

function MyPanel(props) {
  // ignore props.children
  return <h1>Oops, no refs for you today!</h1>;
}

Sekali lagi, ini bukan bug: tidak akan ada apa-apa bagi React untuk menyetel ref karena elemen DOM tidak dibuat .


Referensi tidak disetel sebelum siklus proses jika mereka diteruskan ke bersarang ReactDOM.render()

Mirip dengan bagian sebelumnya, jika Anda meneruskan anak dengan ref ke komponen lain, mungkin saja komponen ini dapat melakukan sesuatu yang mencegah pelampiran ref pada waktunya.

Misalnya, mungkin itu tidak mengembalikan anak dari render(), dan sebaliknya memanggil ReactDOM.render()dalam pengait siklus hidup. Anda dapat menemukan contohnya di sini . Dalam contoh itu, kami merender:

<MyModal>
  <div ref={this.setRef} />
</MyModal>

Tapi MyModalmelakukan ReactDOM.render()panggilan nya componentDidUpdate metode siklus hidup:

componentDidUpdate() {
  ReactDOM.render(this.props.children, this.targetEl);
}

render() {
  return null;
}

Sejak React 16, panggilan render tingkat atas tersebut selama siklus proses akan ditunda hingga siklus proses berjalan untuk keseluruhan pohon . Ini akan menjelaskan mengapa Anda tidak melihat wasit terlampir tepat waktu.

Solusi untuk masalah ini adalah menggunakan portal, bukan ReactDOM.renderpanggilan bersarang :

render() {
  return ReactDOM.createPortal(this.props.children, this.targetEl);
}

Dengan cara ini kita <div>dengan ref sebenarnya disertakan dalam keluaran render.

Jadi jika Anda mengalami masalah ini, Anda perlu memverifikasi tidak ada apa pun antara komponen Anda dan ref yang mungkin menunda rendering turunan.

Jangan gunakan setStateuntuk menyimpan referensi

Pastikan Anda tidak menggunakan setStateuntuk menyimpan ref di callback ref, karena asinkron dan sebelum "selesai", componentDidMountakan dieksekusi terlebih dahulu.


Masih Masalah?

Jika tidak ada tip di atas yang membantu, ajukan masalah di React dan kami akan memeriksanya.

Dan Abramov
sumber
2
Saya telah mengedit jawaban saya untuk menjelaskan situasi ini juga. Lihat bagian pertama. Semoga ini membantu!
Dan Abramov
Hai @DanAbramov, terima kasih untuk ini! Sayangnya saya tidak dapat mengembangkan kasus yang dapat direproduksi ketika saya pertama kali menemukannya. Sayangnya saya tidak lagi mengerjakan proyek itu dan tidak dapat melapor lagi sejak saat itu. Pertanyaannya telah menjadi cukup populer, sehingga saya setuju, mencoba menemukan case yang dapat direproduksi adalah kuncinya karena banyak orang tampaknya mengalami masalah tersebut.
quickshiftin
Saya pikir dalam banyak kasus hal ini mungkin disebabkan oleh kesalahpahaman. Di React 15 ini juga bisa terjadi karena kesalahan yang tertelan (React 16 memiliki penanganan kesalahan yang lebih baik dan mencegahnya). Saya dengan senang hati meninjau lebih banyak kasus ketika ini terjadi, jadi silakan menambahkannya di komentar.
Dan Abramov
Membantu! Saya tidak terlalu memperhatikan bahwa ada prapemuat.
Nazariy
1
Jawaban ini sangat membantu saya. Saya berjuang dengan beberapa referensi "ref" kosong, dan ternyata "elemen" tidak diberikan sama sekali.
MarkSkayff
1

Pengamatan yang berbeda dari masalah tersebut.

Saya menyadari bahwa masalah hanya terjadi saat dalam mode pengembangan. Setelah penyelidikan lebih lanjut, saya menemukan bahwa menonaktifkan react-hot-loaderkonfigurasi Webpack saya mencegah masalah ini.

saya menggunakan

  • "react-hot-loader": "3.1.3"
  • "webpack": "4.10.2",

Dan itu adalah aplikasi elektron.

Konfigurasi pengembangan Webpack parsial saya

const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.config.base')

module.exports = merge(baseConfig, {

  entry: [
    // REMOVED THIS -> 'react-hot-loader/patch',
    `webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`,
    '@babel/polyfill',
    './app/index'
  ],
  ...
})

Menjadi mencurigakan ketika saya melihat bahwa menggunakan fungsi sebaris di render () berhasil, tetapi menggunakan metode terikat macet.

Bekerja dalam hal apa pun

class MyComponent {
  render () {
    return (
      <input ref={(el) => {this.inputField = el}}/>
    )
  }
}

Crash dengan react-hot-loader (ref tidak ditentukan di componentDidMount)

class MyComponent {
  constructor (props) {
    super(props)
    this.inputRef = this.inputRef.bind(this)
  }

  inputRef (input) {
    this.inputField = input
  }

  render () {
    return (
      <input ref={this.inputRef}/>
    )
  }
}

Sejujurnya, hot reload sering kali bermasalah untuk mendapatkan yang "benar". Dengan alat pengembang yang diperbarui dengan cepat, setiap proyek memiliki konfigurasi yang berbeda. Mungkin konfigurasi khusus saya bisa diperbaiki. Saya akan memberi tahu Anda di sini jika itu masalahnya.

Kev
sumber
Ini mungkin menjelaskan mengapa saya mengalami masalah dengan ini di CodePen, tetapi menggunakan fungsi inline tidak membantu dalam kasus saya.
robartsd
0

Masalah ini juga dapat muncul saat Anda mencoba menggunakan ref dari komponen yang dilepas seperti menggunakan ref dalam setinterval dan tidak menghapus interval set selama pelepasan komponen.

componentDidMount(){
    interval_holder = setInterval(() => {
    this.myref = "something";//accessing ref of a component
    }, 2000);
  }

selalu hapus interval seperti misalnya,

componentWillUnmount(){
    clearInterval(interval_holder)
}
pembuat kode acak
sumber