Подтвердить что ты не робот

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

Предположим, что у меня есть компонент вида, который имеет условный рендеринг:

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

Кажется, все работает нормально. Похож, что "Реакт" просто не может отличить "название работы" и "причина безработицы".

Скажите, пожалуйста, что я делаю неправильно...

4b9b3361

Ответ 1

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

Когда React выполняет diff, он определяет, какие компоненты являются новыми, а какие - старыми, на основании их 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 выполнит diff, он увидит, что divs отличается, и повторно отобразит его, включая все его дочерние элементы (1-й пример). Во втором примере разница будет успешной по job-title и unemployment-reason поскольку теперь у них разные ключи.

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


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

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


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

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

Ответ 2

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

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

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

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

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

Ответ 3

Используйте 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>
        );
    }
}