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

时间:2015-01-09 16:28:21

标签: javascript reactjs

我有以下结构:

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

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

我考虑过存储有关FieldEditor状态之外的字段的信息,而是将其置于FormEditor的状态。但是,这需要FormEditor收听每个FieldEditor组件,因为它们会更改并将信息存储在其状态中。

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

5 个答案:

答案 0 :(得分:153)

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

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

使用React.createRef()

这是目前推荐使用React 16.3引用的方法(更多信息请参见the docs)。如果您使用的是早期版本,请参阅下面有关回调参考的信息。

您需要在父组件的构造函数中创建一个新引用,然后通过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元素而不是组件实例。< / p>

回调参考

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

例如:

<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的page

请务必阅读“Don't Overuse Refs”部分,其中说明您不应该使用孩子的state来“让事情发生”。

希望这有助于^ _ ^

修改:添加了React.createRef()方法来创建引用。删除了ES5代码。

答案 1 :(得分:105)

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

这可能与此类似:

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

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

编辑:只是为了它,我已经为任何感兴趣的人使用钩子重写了上面的例子:

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

答案 2 :(得分:13)

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

Aaaa和我们完成了,仅此两个粘液功能组件,我们的最终目标就是“访问”我们的孩子FieldEditor的值,并在我们的父母中炫耀它。

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

希望我的回答可以帮助您学习和了解有关钩子的更多信息,以及是否要检查working example here it is

答案 3 :(得分:9)

如先前的回答所述,请尝试将状态移至顶部组件,并通过传递给其子级的回调来修改状态。

如果您确实需要访问声明为功能组件(挂钩)的子状态,则可以在父组件中声明 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();

答案 4 :(得分:2)

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

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

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

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

int increment = 1;
int para_1_courant = 10;
int para_2_courant = 4;
int para_1_min = 5;
int para_1_max = 10;

int para_2_min = 1;
int para_2_max = 4;

int tab_para_automate[2][2] = {{0}};
int tab_para_application[1][3] = {{0}};

tab_para_automate[0][0] = para_1_min;
tab_para_automate[0][1] = para_1_max;
tab_para_automate[1][0] = para_2_min;
tab_para_automate[1][1] = para_2_max;
printf("coucou1");

tab_para_application[0][0] = para_1_courant;
tab_para_application[0][1] = para_2_courant;
tab_para_application[0][2] = increment;
printf("coucou2\n");
int k,l;

for (k=0 ; k<1 ; k++)
{
    for (l=0 ; l<1 ; l++)
    {
        printf("%d\n", tab_para_automate[k][l]);
    }
}