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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27864951/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 00:48:43  来源:igfitidea点击:

How to access child's state in React?

javascriptreactjs

提问by Moshe Revah

I have the following structure:

我有以下结构:

FormEditor- holds multiple FieldEditor FieldEditor- edits a field of the form and saving various values about it in it's state

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

When a button is clicked within FormEditor, I want to be able to collect information about the fields from all FieldEditorcomponents, information that's in their state, and have it all within FormEditor.

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

I considered storing the information about the fields outside of FieldEditor's state and put it in FormEditor's state instead. However, that would require FormEditorto listen to each of it's FieldEditorcomponents as they change and store their information in it's state.

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

Can't I just access the children's state instead? Is it ideal?

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

采纳答案by Markus-ipse

If you already have onChange handler for the individual FieldEditors I don't see why you couldn't just move the state up to the FormEditor component and just pass down a callback from there to the FieldEditors that will update the parent state. That seems like a more React-y way to do it, to me.

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

Something along the line of this perhaps:

可能是这样的:

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

Original - pre-hooks version:

原始 - 预挂钩版本:

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

回答by Martoid Prime

Just before I go into detail about how you can access the state of a child component, please make sure to read Markus-ipse's answer regarding a better solution to handle this particular scenario.

在我详细介绍如何访问子组件的状态之前,请务必阅读Markus-ipse的回答,了解处理此特定场景的更好解决方案。

If you do indeed wish to access the state of a component's children, you can assign a property called refto each child. There are now two ways to implement references: Using React.createRef()and callback refs.

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

Using React.createRef()

使用 React.createRef()

This is currently the recommended way to use references as of React 16.3 (See the docsfor more info). If you're using an earlier version then see below regarding callback references.

从 React 16.3 开始,这是目前推荐的使用引用的方式(有关更多信息,请参阅文档)。如果您使用的是早期版本,请参阅下面有关回调引用的信息。

You'll need to create a new reference in the constructor of your parent component and then assign it to a child via the refattribute.

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

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

In order to access this kind of ref, you'll need to use:

为了访问这种引用,您需要使用:

const currentFieldEditor1 = this.FieldEditor1.current;

This will return an instance of the mounted component so you can then use currentFieldEditor1.stateto access the state.

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

Just a quick note to say that if you use these references on a DOM node instead of a component (e.g. <div ref={this.divRef} />) then this.divRef.currentwill return the underlying DOM element instead of a component instance.

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

Callback Refs

回调引用

This property takes a callback function that is passed a reference to the attached component. This callback is executed immediately after the component is mounted or unmounted.

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

For example:

例如:

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

In these examples the reference is stored on the parent component. To call this component in your code, you can use:

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

this.fieldEditor1

and then use this.fieldEditor1.stateto get the state.

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

One thing to note, make sure your child component has rendered before you try to access it ^_^

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

As above, if you use these references on a DOM node instead of a component (e.g. <div ref={(divRef) => {this.myDiv = divRef;}} />) then this.divRefwill return the underlying DOM element instead of a component instance.

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

Further Information

更多信息

If you want to read more about React's ref property, check out this pagefrom Facebook.

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

Make sure you read the "Don't Overuse Refs" section that says that you shouldn't use the child's stateto "make things happen".

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

Hope this helps ^_^

希望这有帮助^_^

Edit: Added React.createRef()method for creating refs. Removed ES5 code.

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

回答by Josep Vidal

Its 2020and lots of you will come here looking for a similar solution but with Hooks( They are great! ) and with latest approaches in terms of code cleanliness and syntax.

它的2020和许多你会来这里寻找一个类似的解决方案,但与(他们都是伟大的!),并与最新的代码的整洁和语法方面的方法。

So as previous answers had stated, the best approach to this kind of problem is to hold the state outside of child component fieldEditor. You could do that in multiple ways.

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

The most "complex" is with global context (state) that both parent and children could access and modify. Its a great solution when components are very deep in the tree hierarchy and so its costly to send props in each level.

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

In this case I think its not worth it, and more simple approach will bring us the results we want, just using the powerful React.useState().

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

Approach with React.useState() hook, way simpler than with Class components

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

As said we will deal with changes and store the data of our child component fieldEditorin our parent fieldForm. To do that we will send a reference to the function that will deal and apply the changes to the fieldFormstate, you could do that with:

如前所述,我们将处理更改并将子组件的数据存储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>
  );
}

Note that React.useState({})will return an array with position 0 being the value specified on call (Empty object in this case), and position 1 being the reference to the function that modifies the value.

请注意,React.useState({})将返回一个数组,其中位置 0 是调用时指定的值(在本例中为 Empty 对象),位置 1 是对修改该值的函数的引用。

Now with the child component, FieldEditor, you don't even need to create a function with a return statement, a lean constant with an arrow function will do!

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

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

Aaaaand we are done, nothing more, with just these two slime functional components we have our end goal "access" our child FieldEditorvalue and show it off in our parent.

Aaaa我们已经完成了,仅此而已,仅凭这两个 Slime 功能组件,我们的最终目标就是“访问”我们的 childFieldEditor值并在我们的 parent 中展示它。

You could check the accepted answer from 5 years ago and see how Hooks made React code leaner (By a lot!).

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

Hope my answer helps you learn and understand more about Hooks, and if you want to check a working example here it is.

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

回答by Dhana

Now You can access the InputField's state which is the child of FormEditor .

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

Basically whenever there is a change in the state of the input field(child) we are getting the value from the event object and then passing this value to the Parent where in the state in the Parent is set.

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

On button click we are just printing the state of the Input fields.

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

The key point here is that we are using the props to get the Input Field's id/value and also to call the functions which are set as attributes on the Input Field while we generate the reusable child Input fields.

这里的关键点是我们使用 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 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);

回答by Mauricio Avenda?o

As the previous answers saids, try to move the state to a top component and modify the state through callbacks passed to it's children.

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

In case that you really need to access to a child state that is declared as a functional component (hooks) you can declare a refin the parent component, then pass it as a refattribute to the child but you need to use React.forwardRefand then the hook useImperativeHandleto declare a function you can call in the parent component.

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

Take a look at the following example:

看看下面的例子:

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

Then you should be able to get myState in the Parent component by calling: myRef.current.getMyState();

然后你应该能够通过调用在 Parent 组件中获取 myState : myRef.current.getMyState();