Как принудительно перемонтировать компоненты React?

104

Допустим, у меня есть компонент представления с условной визуализацией:

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 выглядит примерно так:

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

Скажем employedтак. Всякий раз, когда я переключаю его на false и другое представление отображается, только unemployment-durationповторно инициализируется. Также unemployment-reasonпредварительно заполняется значением из job-title(если значение было задано до изменения условия).

Если я изменю разметку во второй процедуре рендеринга на что-то вроде этого:

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

Вроде все нормально работает. Похоже, React просто не умеет различать «должность» и «причину безработицы».

Подскажите пожалуйста, что я делаю не так ...

o01
источник
1
Что находится data-reactidна каждом из MyInput(или input, как видно в DOM) элементах до и после employedпереключения?
Крис
@Chris Мне не удалось указать, что компонент MyInput отображает ввод, заключенный в файл <div>. Эти data-reactidатрибуты , как представляется, отличается как на оберточной DIV и поле ввода. job-titleinput получает идентификатор data-reactid=".0.1.1.0.1.0.1", а unemployment-reasoninput получает идентификаторdata-reactid=".0.1.1.0.1.2.1"
o01
1
а о чем unemployment-duration?
Крис
@Chris Извини, я заговорил слишком рано. В первом примере (без пролета «дифф меня») эти reactidатрибуты одинаковы по job-titleи unemployment-reason, в то время как во втором примере (с пролетом дифф) они отличаются.
o01
@ Крис для unemployment-durationв reactidатрибуте всегда уникален.
o01

Ответы:

109

Что, вероятно, происходит, так это то, что React считает, что между рендерами добавляется только один MyInput( unemployment-duration). Таким образом, job-titleникогда не заменяется на unemployment-reason, поэтому также меняются местами предопределенные значения.

Когда React выполняет сравнение, он определяет, какие компоненты новые, а какие старые, на основе их keyсвойств. Если в коде такого ключа нет, он сгенерирует свой собственный.

Причина, по которой последний предоставленный вами фрагмент кода работает, заключается в том, что React по существу должен изменить иерархию всех элементов в родительском элементе, divи я считаю, что это вызовет повторный рендеринг всех дочерних элементов (вот почему он работает). Если бы вы добавили spanэлемент внизу, а не вверху, иерархия предыдущих элементов не изменилась бы, и эти элементы не перерисовывались бы (и проблема не исчезла бы).

Вот что говорится в официальной документации React :

Ситуация усложняется, когда дочерние элементы перемещаются (как в результатах поиска) или если новые компоненты добавляются в начало списка (как в потоках). В тех случаях, когда идентичность и состояние каждого дочернего элемента должны поддерживаться на всех этапах рендеринга, вы можете однозначно идентифицировать каждого дочернего элемента, назначив ему ключ.

Когда React согласовывает дочерние элементы с ключом, он гарантирует, что любой дочерний элемент с ключом будет переупорядочен (а не уничтожен) или уничтожен (вместо повторного использования).

Вы должны иметь возможность исправить это, предоставив уникальный keyэлемент самому родительскому элементу divили всем MyInputэлементам.

Например:

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

ИЛИ

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

Теперь, когда React выполнит сравнение, он увидит, что divsони разные, и повторно отобразит его, включая всех своих дочерних элементов (1-й пример). Во 2 - ом примере, разница будет иметь успех на job-titleи unemployment-reasonтак как они теперь имеют разные ключи.

Конечно, вы можете использовать любые ключи, если они уникальны.


Обновление август 2017 г.

Чтобы лучше понять, как работают ключи в React, я настоятельно рекомендую прочитать мой ответ на тему «Понимание уникальных ключей в React.js» .


Обновление ноябрь 2017 г.

Это обновление должно было быть опубликовано некоторое время назад, но refтеперь использование строковых литералов в нем не рекомендуется. Например, ref="job-title"теперь должно быть ref={(el) => this.jobTitleRef = el}(например). См. Мой ответ на предупреждение об устаревании с помощью this.refs для получения дополнительной информации.

Крис
источник
10
it will determine which components are new and which are old based on their key property.- это было ... ключом ... к моему пониманию
Ларри
1
Большое тебе спасибо ! Я также понял, что все дочерние компоненты карты были успешно отрисованы, и я не мог понять почему.
ValentinVoilean
хороший совет - использовать переменную состояния (которая изменяется, например, как идентификатор) в качестве ключа для div!
Macilias
202

Измените ключ компонента.

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

Компонент будет размонтирован, и новый экземпляр Компонента будет смонтирован, так как ключ изменился.

изменить : Документировано, что вам, вероятно, не нужно производное состояние :

При изменении ключа React создаст новый экземпляр компонента, а не обновит текущий. Ключи обычно используются для динамических списков, но они также полезны здесь.

Алекс К
источник
46
Это должно быть более очевидно в документации React. Это достигло именно того, что я искал, но на поиски потребовались часы.
Пол
4
Что ж, это мне очень помогло! Спасибо! Мне нужно было сбросить CSS-анимацию, но единственный способ сделать это - принудительно выполнить повторную визуализацию. Я согласен, что это должно быть более очевидно в документации по реакции
Alex. P.
@ Alex.P. Ницца! CSS-анимация иногда бывает сложной. Мне было бы интересно увидеть пример.
Alex K
1
Документация React, описывающая эту технику: reactjs.org/blog/2018/06/07/…
GameSalutes
Спасибо. Я так благодарен за это. Я пробовал вводить случайные числа в необъявленные реквизиты, такие как "randomNumber", но единственное, что сработало, было ключевым.
ShameWare
5

Используйте setStateв своем представлении, чтобы изменить employedсвойство состояния. Это пример движка рендеринга 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>
        );
    }
}
Дмитрий
источник
Я уже этим занимаюсь. Переменная «Используемая» является частью состояния компонентов представления и изменяется с помощью функции setState. Извините за то, что не объяснил.
o01
Переменная "занятая" должна быть свойством состояния React. В вашем примере кода это не очевидно.
Дмитрий
как изменить this.state.employed? Покажите, пожалуйста, код. Вы уверены, что используете setState в нужном месте в своем коде?
Дмитрий
Компонент представления немного сложнее, чем показывает мой пример. В дополнение к MyInput-компонентам он также отображает группу радиокнопок, которая управляет значением 'used' с помощью обработчика onChange. В обработчике onChange я соответствующим образом установил состояние компонента представления. Представление перерисовывается нормально при изменении значения группы радиокнопок, но React считает, что первый MyInput-компонент такой же, как и до изменения параметра «используется».
o01
0

Я работаю над Crud для своего приложения. Вот как я это сделал. Получил Reactstrap в качестве зависимости.

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

Некоторые React Hooks там

Рубен Верстер
источник
Добро пожаловать в SO! Возможно, вы захотите повторить, как написать хороший ответ . Добавьте объяснение того, как вы решили вопрос, помимо публикации кода.
displacedtexan