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

IT技术 javascript reactjs
2021-01-19 22:46:02

我有以下结构:

FormEditor- 拥有多个 FieldEditor 实例 FieldEditor- 编辑表单的一个字段并在其状态下保存有关它的各种值

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

我考虑将有关字段的信息存储FieldEditorFormEditor's 状态之外,而是将其放入's 状态。但是,这需要FormEditor在每个FieldEditor组件发生变化时监听它们并将其信息存储在其状态中。

我不能只访问孩子的状态吗?理想吗?

5个回答

在我详细介绍如何访问子组件的状态之前,请务必阅读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来“使事情发生”。

我只得到在构造函数中定义的状态(不是最新状态)
2021-03-15 22:46:02
现在使用 refs 被归类为使用“不受控制的组件”,建议使用“受控制的组件”
2021-03-20 22:46:02
也是整洁的小花絮,您可以从this.refs.yourComponentNameHere. 我发现它对于通过函数改变状态很有用。例子: this.refs.textInputField.clearInput();
2021-03-25 22:46:02
如果子组件是 Reduxconnected组件,是否可以这样做
2021-03-26 22:46:02
当心(来自文档):Refs 是一种向特定子实例发送消息的好方法,而这种方式通过流式 Reactivepropsstate. 但是,它们不应该是您在应用程序中流动数据的首选抽象。默认情况下,使用 Reactive 数据流并保存refs 用于固有非反应性的用例。
2021-04-08 22:46:02

如果您已经有一个用于各个 FieldEditor 的 onChange 处理程序,我不明白为什么您不能将状态向上移动到 FormEditor 组件,然后将回调从那里传递给将更新父状态的 FieldEditor。对我来说,这似乎是一种更符合 React 的方式。

可能是这样的:

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

原始 - 预挂钩版本:

@190290000RubleMan 似乎与原始问题不同。打开一个包含更多细节的新问题,也许还有一些示例代码,我可以稍后看看:)
2021-03-13 22:46:02
@190290000RubleMan 我不确定您的意思,但由于各个字段上的 onChange 处理程序确保您始终在 FormEditor 组件中的状态下拥有完整的表单数据,因此您的 onSubmit 将只包含类似myAPI.SaveStuff(this.state);. 如果你再详细一点,也许我可以给你一个更好的答案:)
2021-03-16 22:46:02
假设我有一个包含 3 个不同类型字段的表单,每个字段都有自己的验证。我想在提交之前验证它们中的每一个。如果验证失败,每个有错误的字段都应该重新渲染。我还没有想出如何使用您提出的解决方案来做到这一点,但也许有办法!
2021-03-19 22:46:02
您可能会发现此答案很有用:它使用状态挂钩,涵盖了应在何处创建状态、如何避免每次击键时重新呈现表单、如何进行字段验证等。
2021-04-02 22:46:02
这对 onChange 很有用,但在 onSubmit 中没那么有用。
2021-04-10 22:46:02

现在是2020 年,你们中的很多人会来这里寻找类似的解决方案,但使用Hooks(它们很棒!)以及代码清洁度和语法方面的最新方法。

因此,正如之前的答案所述,解决此类问题的最佳方法是将状态保持在子组件之外fieldEditor你可以通过多种方式做到这一点。

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

在这种情况下我认为不值得,更简单的方法会给我们带来我们想要的结果,只需使用强大的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 是调用时指定的值(在本例中为 Empty 对象),位置 1 是对修改该值的函数的引用。

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

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

Aaaaand 我们完成了,仅此而已。仅通过这两个纤细的功能组件,我们的最终目标就是“访问”我们的孩子FieldEditorvalue并在我们的父母中展示它。

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

希望我的回答可以帮助您更多地了解和了解 Hooks,如果您想在这里查看一个工作示例,它是.

什么是“粘液功能成分”
2021-03-21 22:46:02
@PeterMortensen 他们可能的意思是“苗条”。
2021-03-26 22:46:02
这种方法的问题在于FieldEditor组件现在无法单独使用。每个潜在的父母都需要实施他们自己的handleChange.
2021-03-28 22:46:02
此外,父母现在重新渲染单个孩子的每个状态变化。
2021-04-11 22:46:02

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

如果你真的需要访问被声明为功能组件(钩子)的子状态,你可以在父组件中声明一个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]);
})

然后你应该能够通过调用在 Parent 组件中获取 myState :

myRef.current.getMyState();

当我ref.current.getMyState()在 DOM 中调用时,这会引发错误ref.current.getMyState is not a function,因为该组件尚未构建。因此,如果需要用于构建 DOM 的状态,恐怕必须选择另一种解决方案。
2021-03-22 22:46:02
这是太棒了。我不喜欢几乎每个 Child to Parent 状态共享模式,这很棒。我不知道 React 可以通过钩子推荐类多长时间。钩子非常快。
2021-03-26 22:46:02

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

基本上,每当输入字段(子项)的状态发生变化时,我们都会从事件对象中获取值,然后将该值传递给父级,在父级中设置了父级的状态。

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

这里的关键点是我们使用 props 来获取输入字段的id/value,并在我们生成可重用的子输入字段时调用在输入字段上设置为属性的函数

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);
不知道我可以赞成这个。您在这里提到的@Markus-ipse 尚未回答的内容是什么?
2021-04-05 22:46:02
OP 提供了使用类而不是函数的语法(如最佳答案中给出的),这是很重要的,因为如果我将这个问题中的最佳答案转换为使用类,大多数阅读此问题的人也不确定正确的语法。抱歉,我什至不知道类/函数的名称
2021-04-05 22:46:02
此外,我认为相同解决方案的更多变化对那些通过反应蛮力强迫方式的人有好处:D
2021-04-12 22:46:02