Как получить доступ к состоянию ребенка в React?

У меня есть следующая структура:

FormEditor - содержит несколько FieldEditorFieldEditor - редактирует поле формы и сохраняет в нем различные значения

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

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

Разве я не могу просто получить доступ к состоянию детей? Это идеально?

6 ответов

Решение

Если у вас уже есть обработчик onChange для отдельных FieldEditors, я не понимаю, почему вы не могли просто переместить состояние в компонент FormEditor и просто передать обратный вызов оттуда в FieldEditors, который обновит родительское состояние. Мне кажется, это более эффективный способ сделать это.

Возможно, что-то в этом роде:

class FieldEditor extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

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

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

class FormEditor extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};

    this.handleFieldChange = this.handleFieldChange.bind(this);
  }

  handleFieldChange(fieldId, value) {
    this.setState({ [fieldId]: value });
  }

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

    return (
      <div>
        {fields}
        <div>{JSON.stringify(this.state)}</div>
      </div>
    );
  }
}

// Convert to class component and add abillity to dynamically add/remove fields by having it in state
const App = () => {
  const fields = ["field1", "field2", "anotherField"];

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

ReactDOM.render(<App />, document.body);

http://jsbin.com/qeyoxobixa/edit?js,output

Редактировать: Вместо того, чтобы передавать элементы FieldEditor как дочерние в FormEditor, я просто передаю список fieldIds и создаю их вместо этого в FormEditor. Это упрощает динамическое добавление FieldEditor и делает метод визуализации FormEditor менее легким.

Если вы хотите получить доступ к состоянию дочерних компонентов, вы можете назначить свойство с именем ref каждому ребенку. Это свойство принимает функцию обратного вызова, которой передается ссылка на присоединенный компонент. Этот обратный вызов выполняется сразу после монтирования или демонтажа компонента.

Например:

ES5:

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

ES6:

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

В этих примерах ссылка хранится на родительском компоненте. Чтобы вызвать этот компонент в своем коде, вы можете использовать:

this.fieldEditor1

а затем использовать this.fieldEditor1.state чтобы получить состояние.

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

Если вы хотите узнать больше о недвижимости React, проверьте эту страницу на Facebook.

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

Надеюсь, это поможет ^_^

Редактировать: Изменен ответ, чтобы использовать ссылки на обратный вызов в отличие от ссылок на строки, так как ссылки на строки, вероятно, будут удалены в будущем выпуске React.js (подробности см. В разделе Ссылки и DOM).

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

В случае, если вам действительно нужен доступ к дочернему состоянию, которое объявлено как функциональный компонент (хуки), вы можете объявить ссылку в родительском компоненте, а затем передать ее как атрибут ref дочернему, но вам нужно использовать React.forwardRef а затем перехватчик useImperativeHandle для объявления функции, которую вы можете вызвать в родительском компоненте.

Взгляните на следующий пример:

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 в родительском компоненте, вызвав:myRef.current.getMyState();

Его 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, подойдет константа Lean со стрелочной функцией!

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

Ааааи мы закончили, не более того, только с этими двумя функциональными компонентами слизи у нас есть наша конечная цель "получить доступ" к нашему ребенку. FieldEditor value и показать это в нашем родителе.

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

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

Теперь вы можете получить доступ к состоянию InputField, которое является дочерним по отношению к FormEditor .

По сути, всякий раз, когда происходит изменение состояния поля ввода (дочернего), мы получаем значение из объекта события и затем передаем это значение в Parent, где устанавливается состояние в Parent.

При нажатии кнопки мы просто печатаем состояние полей ввода.

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

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

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

      const Parent = () => {
  return (
    <Child onSubmit={(arg) => { 
             console.log('accessing child state from parent callback: ', arg) 
           }} 
    /> 
  )
}

const Child = ({onSubmit}) => {
    const [text, setText] = useState('');

    return (
      <>
        <input value={text} onChange={setText}>
        <button onClick={() => onSubmit(search)} />
      </>
    )
}

Теперь, если вы нажмете кнопку в дочернем компоненте, вы выполните функцию, переданную от родителя, и получите доступ к переменным состояния дочернего компонента.

Другие вопросы по тегам