Bagaimana cara memaksa remounting pada komponen React?

103

Katakanlah saya memiliki komponen tampilan yang memiliki render bersyarat:

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

MyInput terlihat seperti ini:

class MyInput extends React.Component {

    ...

    render(){
        return (
            <div>
                <input name={this.props.name} 
                    ref="input" 
                    type="text" 
                    value={this.props.value || null}
                    onBlur={this.handleBlur.bind(this)}
                    onChange={this.handleTyping.bind(this)} />
            </div>
        );
    }
}

Katakanlah employeditu benar. Setiap kali saya mengalihkannya ke false dan tampilan lain merender, hanya unemployment-durationdiinisialisasi ulang. Juga unemployment-reasonakan diisi sebelumnya dengan nilai dari job-title(jika nilai diberikan sebelum kondisi berubah).

Jika saya mengubah markup dalam rutinitas rendering kedua menjadi seperti ini:

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <span>Diff me!</span>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

Sepertinya semuanya bekerja dengan baik. Sepertinya React gagal membedakan 'job-title' dan 'pengangguran-reason'.

Tolong beritahu saya apa yang saya lakukan salah ...

o01
sumber
1
Apa yang ada data-reactiddi setiap elemen MyInput(atau input, seperti yang terlihat di DOM) sebelum dan sesudah employedpengalihan?
Chris
@Chris Saya gagal menentukan bahwa komponen MyInput membuat input yang dibungkus dalam a <div>. The data-reactidatribut tampaknya berbeda pada kedua div pembungkus dan field input. job-titlemasukan mendapat id data-reactid=".0.1.1.0.1.0.1", sedangkan unemployment-reasonmasukan mendapat iddata-reactid=".0.1.1.0.1.2.1"
o01
1
dan bagaimana dengan unemployment-duration?
Chris
@ Chris Maaf, saya berbicara terlalu cepat. Pada contoh pertama (tanpa span "diff me") reactidatributnya identik job-titledan unemployment-reason, sedangkan pada contoh kedua (dengan span diff) atributnya berbeda.
o01
@ Chris untuk unemployment-durationpara reactidatribut selalu unik.
o01

Jawaban:

108

Apa yang mungkin terjadi adalah React berpikir bahwa hanya satu MyInput( unemployment-duration) yang ditambahkan di antara render. Dengan demikian, job-titletidak pernah diganti dengan unemployment-reason, yang juga mengapa nilai yang telah ditentukan sebelumnya ditukar.

Ketika React melakukan diff, itu akan menentukan komponen mana yang baru dan mana yang lama berdasarkan keypropertinya. Jika tidak ada kunci seperti itu yang diberikan dalam kode, itu akan membuatnya sendiri.

Alasan mengapa potongan kode terakhir yang Anda berikan berfungsi adalah karena React pada dasarnya perlu mengubah hierarki semua elemen di bawah induk divdan saya yakin itu akan memicu render ulang semua anak (itulah mengapa ini berfungsi). Jika Anda menambahkan bagian spanbawah dan bukan bagian atas, hierarki elemen sebelumnya tidak akan berubah, dan elemen tersebut tidak akan dirender ulang (dan masalah akan tetap ada).

Inilah yang dikatakan dokumentasi resmi React :

Situasi menjadi lebih rumit ketika anak-anak diacak (seperti dalam hasil pencarian) atau jika komponen baru ditambahkan ke bagian depan daftar (seperti dalam aliran). Dalam kasus ini di mana identitas dan status setiap anak harus dipertahankan di seluruh render pass, Anda dapat mengidentifikasi setiap anak secara unik dengan menetapkannya sebagai kunci.

Ketika React merekonsiliasi anak-anak yang dikunci, itu akan memastikan bahwa setiap anak dengan kunci akan disusun ulang (bukan di-clobber) atau dihancurkan (bukan digunakan kembali).

Anda harus dapat memperbaikinya dengan menyediakan keysendiri elemen unik untuk induk divatau semua MyInputelemen.

Sebagai contoh:

render(){
    if (this.state.employed) {
        return (
            <div key="employed">
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div key="notEmployed">
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

ATAU

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput key="title" ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <MyInput key="reason" ref="unemployment-reason" name="unemployment-reason" />
                <MyInput key="duration" ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

Sekarang, ketika React melakukan diff, itu akan melihat bahwa divsberbeda dan akan merender ulang termasuk semua anaknya (contoh pertama). Dalam contoh ke-2, diff akan berhasil job-titledan unemployment-reasonkarena mereka sekarang memiliki kunci yang berbeda.

Anda tentu saja dapat menggunakan kunci apa pun yang Anda inginkan, asalkan unik.


Perbarui Agustus 2017

Untuk pemahaman yang lebih baik tentang bagaimana kunci bekerja di React, saya sangat menyarankan membaca jawaban saya untuk Memahami kunci unik di React.js .


Perbarui November 2017

Pembaruan ini seharusnya telah diposting beberapa waktu yang lalu, tetapi menggunakan string literal refsekarang sudah tidak digunakan lagi. Misalnya ref="job-title"sekarang sebagai gantinya ref={(el) => this.jobTitleRef = el}(misalnya). Lihat jawaban saya untuk peringatan Deprecation using this.refs untuk info lebih lanjut.

Chris
sumber
10
it will determine which components are new and which are old based on their key property.- itu adalah ... kunci ... pemahaman saya
Larry
1
Terima kasih banyak ! Saya juga telah mengetahui bahwa semua komponen anak dari peta berhasil dirender ulang dan saya tidak mengerti mengapa.
ValentinVoilean
petunjuk yang bagus adalah menggunakan variabel status (yang berubah, seperti id misalnya) sebagai kunci untuk div!
Macilias
200

Ubah kunci komponen.

<Component key="1" />
<Component key="2" />

Komponen akan dilepas dan contoh baru Komponen akan dipasang karena kunci telah berubah.

sunting : Didokumentasikan pada Anda Mungkin Tidak Perlu Status Berasal :

Ketika sebuah kunci berubah, React akan membuat sebuah instance komponen baru daripada mengupdate yang sekarang. Tombol biasanya digunakan untuk daftar dinamis tetapi juga berguna di sini.

Alex K
sumber
45
Ini seharusnya jauh lebih jelas dalam dokumentasi React. Ini mencapai apa yang saya cari, tetapi membutuhkan waktu berjam-jam untuk menemukannya.
Paul
4
Ini sangat membantu saya! Terima kasih! Saya perlu mengatur ulang animasi CSS tetapi satu-satunya cara untuk melakukannya adalah dengan memaksa render ulang. Saya setuju bahwa ini harus lebih jelas dalam dokumentasi react
Alex. P.
@ Alex.P. Bagus! Animasi CSS terkadang bisa rumit. Saya tertarik untuk melihat contohnya.
Alex K
1
Dokumentasi React yang menjelaskan teknik ini: reactjs.org/blog/2018/06/07/…
GameSalutes
Terima kasih. Saya sangat bersyukur untuk ini. Saya mencoba memasukkan nomor acak ke properti yang tidak dideklarasikan seperti "randomNumber" tetapi satu-satunya properti yang berhasil adalah kuncinya.
ShameWare
5

Gunakan setStatedalam pandangan Anda untuk mengubah employedproperti negara. Ini adalah contoh mesin render React.

 someFunctionWhichChangeParamEmployed(isEmployed) {
      this.setState({
          employed: isEmployed
      });
 }

 getInitialState() {
      return {
          employed: true
      }
 },

 render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <span>Diff me!</span>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}
Dmitriy
sumber
Saya sudah melakukan ini. Variabel 'dipekerjakan' adalah bagian dari status komponen tampilan, dan diubah melalui fungsi setState. Maaf karena tidak menjelaskannya.
o01
variabel 'dipekerjakan' harus merupakan properti dari status React. Tidak jelas dalam contoh kode Anda.
Dmitriy
bagaimana Anda mengubah this.state.employed? Tolong tunjukkan kodenya. Apakah Anda yakin bahwa Anda menggunakan setState di tempat yang tepat di kode Anda?
Dmitriy
Komponen tampilan sedikit lebih kompleks daripada yang ditunjukkan contoh saya. Selain komponen-MyInput, ini juga membuat grup tombol radio yang mengontrol nilai 'digunakan' dengan penangan onChange. Dalam handler onChange, saya menyetel status komponen tampilan yang sesuai. Tampilan ditampilkan kembali dengan baik saat nilai grup tombol radio berubah, tetapi React menganggap komponen MyInput pertama sama dengan sebelum 'digunakan' diubah.
o01
0

Saya sedang mengerjakan Crud untuk aplikasi saya. Ini adalah bagaimana saya melakukannya Got Reactstrap sebagai ketergantungan saya.

import React, { useState, setState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import firebase from 'firebase';
// import { LifeCrud } from '../CRUD/Crud';
import { Row, Card, Col, Button } from 'reactstrap';
import InsuranceActionInput from '../CRUD/InsuranceActionInput';

const LifeActionCreate = () => {
  let [newLifeActionLabel, setNewLifeActionLabel] = React.useState();

  const onCreate = e => {
    const db = firebase.firestore();

    db.collection('actions').add({
      label: newLifeActionLabel
    });
    alert('New Life Insurance Added');
    setNewLifeActionLabel('');
  };

  return (
    <Card style={{ padding: '15px' }}>
      <form onSubmit={onCreate}>
        <label>Name</label>
        <input
          value={newLifeActionLabel}
          onChange={e => {
            setNewLifeActionLabel(e.target.value);
          }}
          placeholder={'Name'}
        />

        <Button onClick={onCreate}>Create</Button>
      </form>
    </Card>
  );
};

Beberapa React Hooks di sana

Ruben Verster
sumber
Selamat datang di SO! Anda mungkin ingin mengulas bagaimana menulis jawaban yang bagus . Tambahkan beberapa penjelasan tentang bagaimana Anda menyelesaikan pertanyaan lebih dari sekadar memposting kode.
displacedtexan