Як отримати доступ до стану дитини в React?


214

У мене така структура:

FormEditor- містить кілька FieldEditor FieldEditor- редагує поле форми і зберігає різні значення про неї у своєму стані

Коли натискається кнопка в FormEditor, я хочу мати змогу збирати інформацію про поля з усіх FieldEditorкомпонентів, інформацію, яка знаходиться у їхньому стані, і мати її все у FormEditor.

Я розглядав можливість зберігати інформацію про поля поза FieldEditorросійською державою і FormEditorзамість цього ставити її у свій стан. Однак для цього потрібно FormEditorбуде прослухати кожен його FieldEditorкомпонент, коли вони змінюються та зберігають свою інформацію у своєму стані.

Чи не можу я просто отримати доступ до стану дітей натомість? Це ідеально?


4
"Чи не можу я просто отримати доступ до стану дітей? Це ідеально?" Ні. Держава - це щось внутрішнє і не повинно просочуватися назовні. Ви можете реалізувати методи аксесуарів для свого компонента, але навіть це не ідеально.
Фелікс Клінг

@FelixKling Тоді ви припускаєте, що ідеальним способом спілкування дитини з батьками є лише події?
Моше Рева

3
Так, події - це один спосіб. Або мати єдиний потік даних, як Flux рекламує: facebook.github.io/flux
Фелікс Клінг

1
Якщо ви не збираєтесь використовувати FieldEditors окремо, збереження їхнього стану FormEditorзвучить добре. Якщо це так, ваші FieldEditorекземпляри будуть відображатися на основі propsпереданих редактором форм, а не їх state. Більш складним, але гнучким способом було б зробити серіалізатор, який проходить через будь-які діти-контейнери та знаходить усі FormEditorекземпляри серед них і серіалізує їх у об'єкт JSON. Об'єкт JSON може бути необов'язково вкладений (більше одного рівня) на основі рівнів вкладення примірників у редакторі форм.
Meglio

4
Я думаю, що "Підняття штату в Документах " React - це, мабуть, самий "реактивний" спосіб цього
icc97

Відповіді:


135

Якщо у вас вже є обробник OnChange для окремих FieldEditors, я не бачу, чому ви не могли просто перенести стан до компонента FormEditor, а просто передати зворотний виклик звідти до FieldEditors, який оновить батьківський стан. Мені це здається більш реагуючим способом зробити це.

Можливо, щось у цьому напрямку:

const FieldEditor = ({ value, onChange, id }) => {
  const handleChange = event => {
    const text = event.target.value;
    onChange(id, text);
  };

  return (
    <div className="field-editor">
      <input onChange={handleChange} value={value} />
    </div>
  );
};

const FormEditor = props => {
  const [values, setValues] = useState({});
  const handleFieldChange = (fieldId, value) => {
    setValues({ ...values, [fieldId]: value });
  };

  const fields = props.fields.map(field => (
    <FieldEditor
      key={field}
      id={field}
      onChange={handleFieldChange}
      value={values[field]}
    />
  ));

  return (
    <div>
      {fields}
      <pre>{JSON.stringify(values, null, 2)}</pre>
    </div>
  );
};

// To add abillity to dynamically add/remove fields keep the list in state
const App = () => {
  const fields = ["field1", "field2", "anotherField"];

  return <FormEditor fields={fields} />;
};

Оригінал - версія перед гачками:


2
Це корисно для OnChange, але не так сильно в OnSubmit.
190290000 Рублева людина

1
@ 190290000RubleMan Я не впевнений, що ви маєте на увазі, але оскільки обробники onChange в окремих полях гарантують, що у вашому штаті в компоненті FormEditor завжди є доступні повні дані форми, ваш OnSubmit просто включає щось подібне myAPI.SaveStuff(this.state);. Якщо ви докладно докладніше, можливо, я можу дати вам кращу відповідь :)
Markus-ipse

Скажіть, у мене форма з 3 полями різного типу, кожне з яких має свою перевірку. Я хотів би підтвердити кожну з них перед надсиланням. Якщо перевірка не вдалася, кожне поле, яке має помилку, має повторно відображатися. Я не зрозумів, як це зробити із запропонованим рішенням, але, можливо, є спосіб!
190290000 Рублева людина

1
@ 190290000RubleMan Виглядає як інший випадок, ніж первісне питання. Відкрийте нове запитання з більш детальною інформацією та, можливо, зразком коду, і я можу переглянути пізніше :)
Markus-ipse

Ця відповідь може бути корисною : вона використовує гачки стану, вона висвітлює, де потрібно створити стан, як уникнути повторного відображення форми на кожному натисканні клавіш, як зробити перевірку поля тощо.
Ендрю

189

Незадовго до того, як я розбираюся в деталях про те, як ви можете отримати доступ до стану дочірнього компонента, переконайтеся, що прочитайте відповідь Маркуса-іпсе щодо кращого рішення для вирішення цього конкретного сценарію.

Якщо ви дійсно бажаєте отримати доступ до стану дітей компонента, ви можете призначити властивість, викликану refкожною дитиною. Зараз існує два способи впровадження посилань: Використання React.createRef()та зворотний зв'язок зворотного дзвінка.

Використання React.createRef()

Наразі це рекомендований спосіб використання посилань на React 16.3 (див . Документи для отримання додаткової інформації). Якщо ви використовуєте більш ранню версію, то див. Нижче щодо посилань на зворотний дзвінок.

Вам потрібно буде створити нове посилання в конструкторі батьківського компонента, а потім призначити його дитині за допомогою refатрибута.

class FormEditor extends React.Component {
  constructor(props) {
    super(props);
    this.FieldEditor1 = React.createRef();
  }
  render() {
    return <FieldEditor ref={this.FieldEditor1} />;
  }
}

Щоб отримати доступ до цього виду, вам потрібно скористатися:

const currentFieldEditor1 = this.FieldEditor1.current;

Це поверне екземпляр змонтованого компонента, щоб потім ви могли використовувати його currentFieldEditor1.stateдля доступу до стану.

Лише коротка примітка, щоб сказати, що якщо ви використовуєте ці посилання на вузлі DOM замість компонента (наприклад <div ref={this.divRef} />), то this.divRef.currentповернете базовий елемент DOM замість компонентного примірника.

Відкликання дзвінків

Це властивість приймає функцію зворотного виклику, яка передається посиланням на доданий компонент. Цей зворотний виклик виконується відразу після встановлення або відключення компонента.

Наприклад:

<FieldEditor
    ref={(fieldEditor1) => {this.fieldEditor1 = fieldEditor1;}
    {...props}
/>

У цих прикладах посилання зберігається на батьківському компоненті. Щоб викликати цей компонент у своєму коді, ви можете використовувати:

this.fieldEditor1

а потім використовувати this.fieldEditor1.stateдля отримання держави.

Варто зазначити одне, переконайтеся, що ваш дочірній компонент відображений, перш ніж ви намагаєтесь отримати доступ до нього ^ _ ^

Як вище, якщо ви використовуєте ці посилання на вузлі DOM замість компонента (наприклад <div ref={(divRef) => {this.myDiv = divRef;}} />), тоді this.divRefповернете базовий елемент DOM замість компонентного примірника.

Детальна інформація

Якщо ви хочете прочитати більше про власність React, перегляньте цю сторінку у Facebook.

Переконайтеся, що ви прочитали розділ " Не перенавантажуйте рефлекси ", в якому йдеться про те, що ви не повинні використовувати дитину для того, stateщоб "робити щось".

Сподіваюся, це допомагає ^ _ ^

Редагувати: Додано React.createRef()метод створення реф. Видалений код ES5.


5
Також акуратний маленький шматочок, з якого ви можете зателефонувати з функцій this.refs.yourComponentNameHere. Я вважаю це корисним для зміни стану за допомогою функцій. Приклад: this.refs.textInputField.clearInput();
Ділан Пірс

7
Остерігайтеся (від документів): Відгуки - це чудовий спосіб надіслати повідомлення певному дочірньому екземпляру таким чином, який було б незручно робити через потокове реагування propsта state. Однак вони не повинні бути вашою абстракцією для передачі даних через вашу програму. За замовчуванням використовуйте Реактивний потік даних і збережіть refs для випадків використання, які за своєю суттю не реагують на реакцію.
Лінн

2
Я отримую лише стан, який було визначено всередині конструктора (не
оновлений

3
Використання refs зараз класифікується як використання " неконтрольованих компонентів " з рекомендацією використовувати "Керовані компоненти"
icc97

Чи можливо це зробити, якщо дочірній компонент є connectedкомпонентом Redux ?
jmancherje

7

Його 2020 рік і багато з вас приїдуть сюди, шукаючи подібне рішення, але з гачками (вони чудові!) Та з останніми підходами щодо чистоти та синтаксису коду.

Отже, як було сказано в попередніх відповідях, найкращим підходом до подібної проблеми є утримання стану поза дочірнім компонентом fieldEditor. Це можна зробити кількома способами.

Найбільш "складний" - це з глобальним контекстом (станом), до якого і батьки, і діти могли отримати доступ та змінювати їх. Це чудове рішення, коли компоненти знаходяться дуже глибоко в ієрархії дерев і тому його дороге відправлення реквізиту на кожному рівні.

У цьому випадку я думаю, що цього не варто, а більш простий підхід принесе нам бажані результати, просто використовуючи потужний React.useState().

Підхід із гачком React.useState (), простіше, ніж з компонентами класу

Як було сказано, ми будемо мати справу зі змінами та зберігати дані нашої дочірньої компоненти fieldEditorв нашому батьків fieldForm. Для цього ми надішлемо посилання на функцію, яка буде обробляти та застосовувати зміни до fieldFormдержави, ви можете це зробити за допомогою:

function FieldForm({ fields }) {
  const [fieldsValues, setFieldsValues] = React.useState({});
  const handleChange = (event, fieldId) => {
    let newFields = { ...fieldsValues };
    newFields[fieldId] = event.target.value;

    setFieldsValues(newFields);
  };

  return (
    <div>
      {fields.map(field => (
        <FieldEditor
          key={field}
          id={field}
          handleChange={handleChange}
          value={fieldsValues[field]}
        />
      ))}
      <div>{JSON.stringify(fieldsValues)}</div>
    </div>
  );
}

Зауважте, що React.useState({})повертає масив із позицією 0 значенням, вказаним при виклику (в цьому випадку порожній об'єкт), а позиція 1 є посиланням на функцію, яка змінює значення.

Тепер із дочірнім компонентом FieldEditorвам навіть не потрібно створювати функцію з оператором return, нахильна константа з функцією стрілки зробить!

const FieldEditor = ({ id, value, handleChange }) => (
  <div className="field-editor">
    <input onChange={event => handleChange(event, id)} value={value} />
  </div>
);

Аааа, і ми готові, нічого більше, тільки з цими двома функціональними компонентами слизу ми маємо свою кінцеву мету «отримати доступ» до нашої дочірньої FieldEditorцінності і показати це в нашому батьків.

Ви можете перевірити прийняту відповідь 5 років тому і побачити, як Гачки змусили реагувати на коди React (на багато!).

Сподіваюся, моя відповідь допоможе вам дізнатися і зрозуміти більше про Гачки, і якщо ви хочете перевірити робочий приклад, ось він .


4

Тепер Ви можете отримати доступ до стану InputField, що є дочіркою FormEditor.

В основному, коли відбувається зміна стану поля введення (дочірнього), ми отримуємо значення від об'єкта події, а потім передаємо це значення Батькові, де встановлено стан у Батьку.

При натисканні на кнопку ми просто друкуємо стан полів введення.

Ключовим моментом тут є те, що ми використовуємо реквізити для отримання id / значення поля введення, а також для виклику функцій, встановлених як атрибути в полі введення, в той час як ми генеруємо дочірні поля введення для повторного використання.

class InputField extends React.Component{
  handleChange = (event)=> {
    const val = event.target.value;
    this.props.onChange(this.props.id , val);
  }

  render() {
    return(
      <div>
        <input type="text" onChange={this.handleChange} value={this.props.value}/>
        <br/><br/>
      </div>
    );
  }
}       


class FormEditorParent extends React.Component {
  state = {};
  handleFieldChange = (inputFieldId , inputFieldValue) => {
    this.setState({[inputFieldId]:inputFieldValue});
  }
  //on Button click simply get the state of the input field
  handleClick = ()=>{
    console.log(JSON.stringify(this.state));
  }

  render() {
    const fields = this.props.fields.map(field => (
      <InputField
        key={field}
        id={field}
        onChange={this.handleFieldChange}
        value={this.state[field]}
      />
    ));

    return (
      <div>
        <div>
          <button onClick={this.handleClick}>Click Me</button>
        </div>
        <div>
          {fields}
        </div>
      </div>
    );
  }
}

const App = () => {
  const fields = ["field1", "field2", "anotherField"];
  return <FormEditorParent fields={fields} />;
};

ReactDOM.render(<App/>, mountNode);

7
Не впевнений, що я можу підтримати це. Що ви тут згадуєте, на що вже не відповів @ Markus-ipse?
Олександр Птах

3

Як говорилося в попередніх відповідях, спробуйте перенести стан на верхній компонент і змінити стан через зворотні виклики, передані дітям.

Якщо вам дійсно потрібен доступ до дочірнього стану, який оголошений як функціональний компонент (гачки), ви можете оголосити посилання на батьківський компонент, а потім передати його як атрибут ref для дитини, але вам потрібно використовувати React.forwardRef а потім гак використовуєImperativeHandle для оголошення функції, яку можна викликати в батьківському компоненті.

Погляньте на наступний приклад:

const Parent = () => {
    const myRef = useRef();
    return <Child ref={myRef} />;
}

const Child = React.forwardRef((props, ref) => {
    const [myState, setMyState] = useState('This is my state!');
    useImperativeHandle(ref, () => ({getMyState: () => {return myState}}), [myState]);
})

Тоді ви маєте змогу отримати myState у компоненті Parent, зателефонувавши: myRef.current.getMyState();

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