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

Mos*_*vah 198 javascript reactjs

我有以下结构:

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

当在FormEditor中单击一个按钮时,我希望能够收集有关所有FieldEditor组件中的字段的信息,状态中的信息,以及在FormEditor中的所有信息.

我考虑存储有关FieldEditor状态之外的字段的信息,并将其置于FormEditor状态.但是,当它们改变并将其信息存储在其状态时,需要FormEditor监听每个FieldEditor组件.

我不能只是访问孩子的州吗?这是理想的吗?

Mar*_*ime 174

在我详细介绍如何访问子组件的状态之前,请务必阅读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} />;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

const currentFieldEditor1 = this.FieldEditor1.current;
Run Code Online (Sandbox Code Playgroud)

这将返回已安装组件的实例,以便您可以使用它currentFieldEditor1.state来访问该状态.

简单地说,如果您在DOM节点而不是组件(例如<div ref={this.divRef} />)上使用这些引用,那么this.divRef.current将返回底层DOM元素而不是组件实例.

回调参考

此属性采用一个回调函数,该函数传递对附加组件的引用.在安装或卸载组件后立即执行此回调.

例如:

<FieldEditor
    ref={(fieldEditor1) => {this.fieldEditor1 = fieldEditor1;}
    {...props}
/>
Run Code Online (Sandbox Code Playgroud)

在这些示例中,引用存储在父组件上.要在代码中调用此组件,您可以使用:

this.fieldEditor1
Run Code Online (Sandbox Code Playgroud)

然后this.fieldEditor1.state用来获得状态.

有一点需要注意,在尝试访问它之前,请确保您的子组件已呈现^ _ ^

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

更多的信息

如果您想了解有关React的ref属性的更多信息,请从Facebook 查看此页面.

请务必阅读" 不要过度使用参考 "部分,该部分说明您不应该使用孩子state来"让事情发生".

希望这有助于^ _ ^

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

  • 请注意(来自文档):*Refs是一种很好的方式,通过流式传输Reactive`道具`和`状态`,以一种不方便的方式向特定的子实例发送消息.但是,它们不应成为您通过应用程序传输数据的首选抽象.默认情况下,使用Reactive数据流并将`ref`s保存为本质上无反应的用例.* (7认同)
  • 同样整洁的小花絮,你可以从`this.refs.yourComponentNameHere`调用函数.我发现通过函数改变状态很有用.示例:`this.refs.textInputField.clearInput();` (5认同)
  • 现在使用refs被归类为使用'[Uncontrolled Components](https://facebook.github.io/react/docs/uncontrolled-components.html)',建议使用'受控组件' (3认同)
  • 我只获得在构造函数中定义的状态(不是最新状态) (2认同)

Mar*_*pse 124

如果您已经拥有单个FieldEditors的onChange处理程序,我不明白为什么您不能将状态移动到FormEditor组件,只是将回调从那里传递给将更新父状态的FieldEditors.对我来说,这似乎是一种更React-y方式.

也许这样的事情:

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 class component and add 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);
Run Code Online (Sandbox Code Playgroud)

http://jsbin.com/qeyoxobixa/edit?js,output

编辑:而不是将FieldEditor元素作为子项传递给FormEditor我只需传递一个fieldIds列表,然后在FormEditor中创建它们.这样可以更容易地动态添加FieldEditors并使FormEditor的render方法更少winkey.

  • 假设我有一个包含 3 个不同类型字段的表单,每个字段都有自己的验证。我想在提交之前验证它们。如果验证失败,则应重新呈现每个有错误的字段。我还没有弄清楚如何使用您提出的解决方案来做到这一点,但也许有办法! (3认同)
  • 这将导致重新渲染整个父组件,不是吗? (3认同)
  • 这对于onChange很有用,但在onSubmit中却没有那么多. (2认同)
  • @190290000RubleMan 我不确定你的意思,但由于各个字段上的 onChange 处理程序确保你在 FormEditor 组件中始终拥有可用的完整表单数据,因此你的 onSubmit 将只包含类似 `myAPI.SaveStuff 的内容(this.state);`. 如果你再详细一点,也许我可以给你更好的答案:) (2认同)

Jos*_*dal 24

现在是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>
  );
}
Run Code Online (Sandbox Code Playgroud)

请注意,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>
);
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 此外,父级现在会在单个子级的每次状态更改时重新渲染。 (4认同)

小智 20

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

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

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

myRef.current.getMyState();


Dha*_*ana 5

现在您可以访问 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);
Run Code Online (Sandbox Code Playgroud)

  • 不知道我可以赞成这个。您在这里提到的@Markus-ipse 尚未回答的内容是什么? (7认同)