Jak wymusić ponowne montowanie na komponentach React?

103

Powiedzmy, że mam składnik widoku, który ma renderowanie warunkowe:

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 wygląda mniej więcej tak:

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>
        );
    }
}

Powiedzmy, że employedto prawda. Za każdym razem, gdy przełączam go na false, a inny widok renderuje się, tylko unemployment-durationjest ponownie inicjowany. unemployment-reasonZostaje również wstępnie wypełniona wartością z job-title(jeśli wartość została podana przed zmianą warunku).

Jeśli zmienię znaczniki w drugiej procedurze renderowania na coś takiego:

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>
        );
    }
}

Wygląda na to, że wszystko działa dobrze. Wygląda na to, że React po prostu nie rozróżnia „tytułu stanowiska” i „powodu bezrobocia”.

Proszę, powiedz mi, co robię źle ...

o01
źródło
1
Co się dzieje w data-reactidkażdym z elementów MyInput(lub input, jak widać w DOM), przed i po employedprzełączeniu?
Chris
@Chris Nie udało mi się określić, że składnik MyInput renderuje dane wejściowe opakowane w <div>. Te data-reactidatrybuty wydaje się być różne na obu div owijania i polu tekstowym. job-titlewejście otrzymuje id data-reactid=".0.1.1.0.1.0.1", podczas gdy unemployment-reasonwejście otrzymuje iddata-reactid=".0.1.1.0.1.2.1"
o01
1
a co z tym unemployment-duration?
Chris
@Chris Przepraszam, powiedziałem zbyt wcześnie. W pierwszym przykładzie (bez przęsła „diff mnie”) to reactidatrybuty są identyczne job-titlei unemployment-reason, podczas gdy w drugim przypadku (przy rozpiętości diff) są one różne.
o01
@Chris dla unemployment-durationtej reactidatrybutu jest zawsze wyjątkowy.
o01

Odpowiedzi:

108

Prawdopodobnie dzieje się tak, że React uważa, że między renderowaniami jest dodawany tylko jeden znak MyInput( unemployment-duration). W związku z tym job-titlenigdy nie jest zastępowane przez unemployment-reason, co jest również powodem zamiany wstępnie zdefiniowanych wartości.

Kiedy React wykona różnicę, określi, które komponenty są nowe, a które stare na podstawie ich keywłaściwości. Jeśli w kodzie nie ma takiego klucza, wygeneruje własny.

Powodem, dla którego ostatni udostępniony fragment kodu działa, jest to, że React zasadniczo musi zmienić hierarchię wszystkich elementów podrzędnym divi uważam, że spowodowałoby to ponowne renderowanie wszystkich dzieci (i dlatego to działa). Gdybyś dodał na spandole zamiast na górze, hierarchia poprzednich elementów nie zmieniłaby się, a te elementy nie byłyby ponownie renderowane (i problem utrzymywałby się).

Oto, co mówi oficjalna dokumentacja React :

Sytuacja komplikuje się bardziej, gdy dzieci są tasowane (jak w wynikach wyszukiwania) lub jeśli nowe komponenty są dodawane na początek listy (jak w strumieniach). W tych przypadkach, w których tożsamość i stan każdego dziecka musi być zachowana we wszystkich przebiegach renderowania, można jednoznacznie zidentyfikować każde dziecko, przypisując mu klucz.

Kiedy React uzgadnia dzieci z kluczem, zapewni, że każde dziecko z kluczem zostanie ponownie uporządkowane (zamiast sklejone) lub zniszczone (zamiast ponownie użyte).

Powinieneś być w stanie to naprawić, dostarczając samodzielnie unikalny keyelement rodzicowi divlub wszystkim MyInputelementom.

Na przykład:

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>
        );
    }
}

LUB

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>
        );
    }
}

Teraz, gdy React wykona divsróżnicę , zobaczy, że są różne i ponownie wyrenderuje je, w tym wszystkie jego dzieci (pierwszy przykład). W 2. przykładzie diff będzie sukces job-title, a unemployment-reasonponieważ mają teraz różne klucze.

Możesz oczywiście używać dowolnych kluczy, o ile są unikalne.


Aktualizacja z sierpnia 2017 r

Aby uzyskać lepszy wgląd w działanie kluczy w React, zdecydowanie polecam przeczytanie mojej odpowiedzi na temat Zrozumienie unikalnych kluczy w React.js .


Aktualizacja z listopada 2017 r

Ta aktualizacja powinna zostać opublikowana jakiś czas temu, ale używanie literałów ciągów w programie refjest obecnie przestarzałe. Na przykład ref="job-title"powinien teraz zamiast ref={(el) => this.jobTitleRef = el}(na przykład). Zobacz moją odpowiedź na ostrzeżenie o wycofaniu przy użyciu this.refs, aby uzyskać więcej informacji.

Chris
źródło
10
it will determine which components are new and which are old based on their key property.- to był ... klucz ... dla mojego zrozumienia
Larry
1
Dziękuję bardzo ! Dowiedziałem się również, że wszystkie komponenty potomne mapy zostały pomyślnie zrenderowane i nie mogłem zrozumieć, dlaczego.
ValentinVoilean
fajną wskazówką jest użycie zmiennej stanu (która zmienia się, jak np. id) jako klucza dla div!
Macilias
200

Zmień klucz komponentu.

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

Komponent zostanie odmontowany, a po zmianie klucza zostanie zamontowana nowa instancja komponentu.

edycja : Udokumentowano, że prawdopodobnie nie potrzebujesz stanu pochodnego :

Kiedy klucz się zmieni, React utworzy nową instancję komponentu zamiast aktualizować obecną. Klucze są zwykle używane w przypadku list dynamicznych, ale są również przydatne tutaj.

Alex K.
źródło
45
To powinno być o wiele bardziej oczywiste w dokumentacji Reacta. Osiągnęło to dokładnie to, o co mi chodziło, ale znalezienie go zajęło godziny.
Paul
4
Cóż, bardzo mi to pomogło! dzięki! Musiałem zresetować animację CSS, ale jedynym sposobem na to jest wymuszenie ponownego renderowania. Zgadzam się, że w dokumentacji reakcji powinno to być bardziej oczywiste
Alex. P.
@ Alex.P. Miły! Animacje CSS mogą czasami być trudne. Chciałbym zobaczyć przykład.
Alex K
1
Dokumentacja React opisująca tę technikę: reactjs.org/blog/2018/06/07/…
GameSalutes
Dziękuję Ci. Jestem za to bardzo wdzięczna. Próbowałem podawać losowe liczby niezadeklarowanym rekwizytom, takim jak „randomNumber”, ale jedynym działającym rekwizytem był klucz.
ShameWare
5

Użyj setStatew swoim widoku, aby zmienić employedwłaściwość stanu. To jest przykład silnika renderującego 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
źródło
Już to robię. Zmienna `` zatrudniona '' jest częścią stanu komponentów widoku i jest zmieniana przez jej funkcję setState. Przepraszam, że tego nie wyjaśniam.
o01
Zmienna „zatrudniona” powinna być własnością stanu React. Nie jest to oczywiste w twoim przykładzie kodu.
Dmitriy
jak zmienić this.state.employed? Pokaż kod, proszę. Czy na pewno używasz setState we właściwym miejscu w swoim kodzie?
Dmitriy
Komponent widoku jest nieco bardziej złożony niż pokazuje mój przykład. Oprócz komponentów MyInput renderuje również grupę radiobutton, która kontroluje wartość „zatrudniony” za pomocą procedury obsługi onChange. W module obsługi onChange odpowiednio ustawiam stan komponentu widoku. Widok ponownie renderuje się dobrze, gdy zmienia się wartość grupy radiobutton, ale React uważa, że ​​pierwszy komponent MyInput jest taki sam, jak przed zmianą „zatrudnionego”.
o01
0

Pracuję nad Crudem dla mojej aplikacji. W ten sposób to zrobiłem Dostałem Reactstrap jako moją zależność.

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>
  );
};

Jest tam kilka haków reakcyjnych

Ruben Verster
źródło
Witamy w SO! Możesz sprawdzić, jak napisać dobrą odpowiedź . Dodaj wyjaśnienie, w jaki sposób rozwiązałeś pytanie, poza zwykłym opublikowaniem kodu.
displacedtexan