如何在React中访问child's state

我有以下结构:

FormEditor -保存FieldEditor的多个实例 FieldEditor -编辑表单的字段,并保存关于它的状态的各种值

当在FormEditor中单击按钮时,我希望能够从所有FieldEditor组件中收集有关字段的信息,这些信息处于它们的状态,并在FormEditor中拥有所有这些信息。

我考虑将有关字段的信息存储在FieldEditor的状态之外,并将其放在FormEditor的状态中。然而,这将要求FormEditor在每个FieldEditor组件更改并将其信息存储在其状态时监听它们。

我不能直接访问子节点的状态吗?理想吗?

286543 次浏览

如果你已经有一个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 the ability to dynamically add/remove fields, keep the list in state
const App = () => {
const fields = ["field1", "field2", "anotherField"];


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

原始-预挂钩版本:

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 a class component and add the ability 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);

在我详细介绍如何访问子组件的状态之前,请务必阅读Markus-ipse关于处理这种特定场景的更好解决方案的答案。

如果你确实希望访问组件的子组件的状态,你可以为每个子组件分配一个名为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的ref属性的内容,请查看Facebook上的这个页面

确保你阅读了&;__abc1 &;部分,说你不应该使用孩子的state来“使事情发生”。

现在你可以访问InputField的状态,它是FormEditor的子状态。

基本上,只要输入字段(child)的状态发生了变化,我们就从事件对象中获取值,然后将这个值传递给父对象,在父对象中设置了状态。

在单击按钮时,我们只是打印输入字段的状态。

这里的关键点是,我们使用props来获取输入字段的 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 a 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);

它是2020,很多人会来这里寻找一个类似的解决方案,但使用钩子(它们很棒!),并且在代码清洁度和语法方面采用了最新的方法。

因此,正如前面的回答所述,解决这类问题的最佳方法是将状态保存在子组件fieldEditor之外。你可以用多种方法来做。

最“复杂”的;具有父级和子级都可以访问和修改的全局上下文(状态)。当组件位于树层次结构的非常深处时,这是一个很好的解决方案,因此在每个关卡中发送道具的成本很高。

在这种情况下,我认为这是不值得的,一个更简单的方法将带来我们想要的结果,只需使用强大的React.useState()

使用React.useState()钩子的方法比使用Class组件简单得多

如前所述,我们将处理变化,并将子组件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>
);

我们做完了,没有别的了。有了这两个小巧的功能组件,我们就有了我们的最终目标“access”;我们的孩子FieldEditor值,并在我们的父母炫耀它。

你可以看看5年前的答案,看看Hooks是如何让React代码变得更精简的(精简了很多!)

希望我的回答能帮助你学习和理解更多关于Hooks的知识,如果你想检查这里是工作示例

正如前面的回答所说,尝试将状态移动到顶部组件,并通过传递给其子组件的回调修改状态。

如果你真的需要访问一个声明为函数组件(钩子)的子状态,你可以在父组件中声明一个裁判,然后将它作为裁判属性传递给子组件,但是你需要使用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();

您可以通过向子组件传递回调来访问子状态。

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

现在,如果单击子组件中的按钮,您将执行从父组件传递过来的函数,并可以访问子组件的状态变量。