如何在 React 中访问 child 的状态

How to access a child's state in React

我有以下结构:

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

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

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

我不能直接访问 children 的状态吗?理想吗?

如果您已经为各个 FieldEditors 提供了 onChange 处理程序,我不明白为什么您不能将状态向上移动到 FormEditor 组件,然后将回调从那里传递给 FieldEditors 以更新母国。对我来说,这似乎是一种更符合 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);

在我详细介绍如何访问 child 组件的状态之前,请务必阅读 关于处理此特定情况的更好解决方案的回答。

如果您确实希望访问组件的 children 的状态,您可以为每个 child 分配一个名为 ref 的 属性。现在有两种实现引用的方法:使用React.createRef()和回调引用。

使用React.createRef()

从 React 16.3 开始,这是目前推荐的使用引用的方式(有关详细信息,请参阅 the documentation)。如果您使用的是早期版本,请参阅下文有关回调参考的内容。

您需要在 parent 组件的构造函数中创建一个新引用,然后通过 ref 属性将其分配给 child。

class FormEditor extends React.Component {
  constructor(props) {
    super(props);
    this.FieldEditor1 = React.createRef();
  }
  render() {
    return <FieldEditor ref={this.FieldEditor1} />;
  }
}

为了访问此类参考,您需要使用:

const currentFieldEditor1 = this.FieldEditor1.current;

这将 return 已安装组件的实例,因此您可以使用 currentFieldEditor1.state 访问状态。

简单说明一下,如果您在 DOM 节点而不是组件(例如 <div ref={this.divRef} />)上使用这些引用,那么 this.divRef.current 将 return基础 DOM 元素而不是组件实例。

回调参考

此 属性 接受一个回调函数,该函数传递一个对附加组件的引用。该回调在组件挂载或卸载后立即执行。

例如:

<FieldEditor
    ref={(fieldEditor1) => {this.fieldEditor1 = fieldEditor1;}
    {...props}
/>

在这些示例中,引用存储在 parent 组件中。要在您的代码中调用此组件,您可以使用:

this.fieldEditor1

然后使用this.fieldEditor1.state获取状态。

有一点要注意,确保你的 child 组件在你尝试访问它之前已经呈现 ^_^

如上所述,如果您在 DOM 节点而不是组件(例如 <div ref={(divRef) => {this.myDiv = divRef;}} />)上使用这些引用,则 this.divRef 将 return 底层 DOM 元素而不是组件实例。

更多信息

如果您想阅读更多关于 React 的参考资料 属性,请查看 Facebook this page

请务必阅读“Don't Overuse Refs”部分,其中指出您不应使用 child 的 state 来“让事情发生”。

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

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

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

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

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

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

如前所述,我们将处理更改并将 child 组件 fieldEditor 的数据存储在 parent 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({}) 将 return 一个数组,位置 0 是调用时指定的值(在这种情况下为空 object),位置 1 是对函数的引用 修改值。

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

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

Aaaa 我们完成了,仅此而已。仅通过这两个纤细的功能组件,我们的最终目标就是“访问”我们的 child FieldEditor 值并在我们的 parent.

中展示它

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

希望我的回答能帮助你学习和了解更多关于Hooks的知识,如果你想查看working example here it is.

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

如果您确实需要访问声明为功能组件(挂钩)的 child 状态,您可以在 parent 组件,然后将其作为 ref 属性传递给 child,但您需要使用 React.forwardRef 然后挂钩 useImperativeHandle 来声明一个可以在 parent 组件中调用的函数。

看看下面的例子:

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

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

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

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