Як примусити переробляти компоненти на React?


103

Скажімо, у мене є компонент перегляду, який має умовне візуалізацію:

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це правда. Щоразу, коли я перемикаю його на помилковий, а інший вигляд відображається, лише 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 просто не в змозі розрізняти "посаду" та "безробіття".

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


1
Що є у data-reactidкожного з MyInput(або input, як це видно в DOM) елементів до і після employedвимикача?
Кріс

@Chris Я не зміг вказати, що компонент MyInput надає вхід, загорнутий у <div>. Ці data-reactidатрибути , як видається, відрізняється як на обгорткового DIV і поле введення. job-titleвхід отримує ідентифікатор data-reactid=".0.1.1.0.1.0.1", а unemployment-reasonвхід отримує iddata-reactid=".0.1.1.0.1.2.1"
o01

1
а про що unemployment-duration?
Кріс

@Chris Вибачте, я говорив занадто рано. У першому прикладі (без прольоту «дифф мене») ці reactidатрибути однакові по job-titleі unemployment-reason, в той час як у другому прикладі (з прольотом дифф) вони відрізняються.
o01

@ Кріс для unemployment-durationв reactidатрибуті завжди унікальний.
o01

Відповіді:


108

Можливо, те, що React думає, що між візуалізаціями додається лише один 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-й приклад). У 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

200

Змініть ключ компонента.

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

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

редагувати : Документовано про вас, ймовірно, не потрібно отриманого стану :

Коли ключ змінюється, React створить новий екземпляр компонента, а не оновить поточний. Клавіші зазвичай використовуються для динамічних списків, але вони також корисні тут.


45
Це повинно бути більш очевидним у документації React. Це досягло саме того, що я хотів, але знадобилося години, щоб знайти.
Пол

4
Ну, це мені дуже допомогло! Дякую! Мені потрібно було скинути анімацію CSS, але єдиний спосіб зробити це - примусити повторне відображення. Я погоджуюся, що це повинно бути очевиднішим у документації про реагування
Олексій. П.

@ Alex.P. Приємно! CSS-анімації часом можуть бути складними. Мені було б цікаво подивитися приклад.
Алекс К

1
Реагуйте документацію, що описує цю техніку: 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>
        );
    }
}

Я це вже роблю. Змінна 'used' є частиною стану компонентів перегляду, і вона змінюється через її функцію setState. Вибачте, що не пояснили цього.
o01

Змінна 'найманий' має бути власністю держави React. Це не очевидно в прикладі вашого коду.
Дмитро

як ви змінюєте this.state.employed? Покажіть код, будь ласка. Ви впевнені, що використовуєте setState належним чином у своєму коді?
Дмитро

Компонент перегляду трохи складніший, ніж показує мій приклад. На додаток до компонентів MyInput, він також представляє групу радіо-кнопок, яка контролює значення "зайнятого" за допомогою обробника 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>
  );
};

Деякі реактивні гачки там


Ласкаво просимо до SO! Ви можете переглянути, як написати хорошу відповідь . Додайте пояснення того, як ви вирішили питання, окрім просто опублікування коду.
переміщенийтексан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.