如何在React中访问孩子的状态?


214

我有以下结构:

FormEditor-包含多个FieldEditor- FieldEditor编辑表单的字段并在其状态下保存有关该字段的各种值

在FormEditor中单击按钮时,我希望能够从所有FieldEditor组件中收集有关字段的信息,处于其状态的信息,并将其全部包含在FormEditor中。

我考虑过将有关字段信息的信息存储在FieldEditor的状态之外,FormEditor而是将其置于的状态。但是,这将需要FormEditorFieldEditor组件的每个组件发生更改时侦听它们并以其状态存储其信息。

我不能只访问儿童州吗?理想吗?


4
“我不能只访问儿童国家吗?理想吗?” 否。状态是内部的东西,不应泄漏到外部。您可以为您的组件实现访问器方法,但那还是不理想的。
菲利克斯·克林

@FelixKling然后,您建议孩子与父母进行交流的理想方式是仅发生事件?
Moshe Revah,2015年

3
是的,事件是一种方式。还是像Flux那样促进一种定向数据流:facebook.github.io/flux
Felix Kling,2015年

1
如果您不打算FieldEditor单独使用s,那么保存它们的状态FormEditor听起来不错。如果是这种情况,您的FieldEditor实例将根据props其表单编辑器(而不是其)传递的结果进行渲染state。一种更复杂但灵活的方法是使序列化器遍历所有容器子级,并FormEditor在其中找到所有实例,然后将其序列化为JSON对象。可以根据实例在表单编辑器中的嵌套级别将JSON对象嵌套(不止一个级别)。
Meglio '16

4
我认为React Docs的“提升状态”可能是最“反应迅速”的方式
icc97'5

Answers:


134

如果您已经具有用于单个FieldEditor的onChange处理函数,那么我不明白为什么您不能仅将状态移至FormEditor组件,而仅将回调从那里传递给FieldEditor,以更新父状态。对我来说,这似乎是一种更React-y的方式。

可能与此类似:

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

在我详细介绍如何访问子组件的状态之前,请确保阅读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上查看此页面

确保阅读了“ 不要过度使用引用”部分,该部分指出您不应该使用孩子的state“使事情发生”。

希望这可以帮助^ _ ^

编辑:添加React.createRef()了创建引用的方法。删除了ES5代码。


5
同样精巧的小技巧,您可以从调用函数this.refs.yourComponentNameHere。我发现它对于通过函数更改状态很有用。示例: this.refs.textInputField.clearInput();
Dylan Pierce

7
当心(来自文档):引用是一种将消息发送到特定子实例的好方法,而通过流式Reactive props和这样做不方便state。但是,它们不应该成为您在应用程序中流动数据的首选方法。默认情况下,使用ref响应式数据流并将s 保存为固有非响应式的用例。
林恩

2
我只得到在构造函数中定义的状态(不是最新状态)
VladoPandžić16年

3
现在,将使用refs归类为使用“ 不受控制的组件 ”,并建议使用“受控制的组件”
icc97

如果子组件是Redux connected组件,是否可以这样做?
jmancherje

6

它的2020年以及许多人将来到这里寻找类似的解决方案,但要使用Hooks(它们很棒!),并提供有关代码整洁度和语法的最新方法。

因此,如先前的回答所述,解决此类问题的最佳方法是将状态保持在子组件之外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是调用时指定的值(在这种情况下为Empty对象),位置1是对修改该值的函数的引用。

现在有了子组件,FieldEditor您甚至不需要创建带有return语句的函数,带有箭头函数的精益常量就可以了!

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

Aaaaand我们已经完成了,仅此两个粘液功能组件,我们的最终目标就是“访问”我们的孩子FieldEditor价值,并在我们的父母中炫耀它。

您可以检查5年前接受的答案,看看Hooks如何使React代码变得更精简(很多!)。

希望我的回答可以帮助您学习和了解有关Hooks的更多信息,如果您想在此处查看一个有效的示例,请参见。


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,然后将其作为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();

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.