Как получить доступ к состоянию ребенка в 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)} />
</>
)
}
Теперь, если вы нажмете кнопку в дочернем компоненте, вы выполните функцию, переданную от родителя, и получите доступ к переменным состояния дочернего компонента.