繁体   English   中英

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

[英]How to access a child's state in React

我有以下结构:

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

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

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

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

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

如果您已经有一个用于各个 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} />;
};

原始 - 预挂钩版本:

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

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

因此,正如之前的答案所述,解决此类问题的最佳方法是将状态保存在子组件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 是调用时指定的值(在本例中为 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,如果你想在这里查看一个工作示例,它是.

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

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

现在您可以访问 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);

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM