javascript 如何在React中访问子级的状态

wnrlj8wa  于 2023-02-07  发布在  Java
关注(0)|答案(6)|浏览(125)

我有以下结构:
FormEditor-保存FieldEditor的多个示例FieldEditor-编辑表单的一个字段,并在其状态中保存有关该字段的各种值
当在FormEditor中单击按钮时,我希望能够从所有FieldEditor组件收集有关字段的信息,这些信息处于它们的状态,并将它们全部保存在FormEditor中。
我考虑过将字段的信息存储在FieldEditor的状态之外,并将其置于FormEditor的状态中,但是,这将要求FormEditor在其每个FieldEditor组件发生变化时侦听它们,并将它们的信息存储在其状态中。
我不能直接进入儿童之州吗?理想吗?

mzaanser

mzaanser1#

在详细讨论如何访问子组件的状态之前,请务必阅读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上查看this page
确保您阅读了"不要过度使用Refs"部分,该部分指出您不应该使用孩子的state来"使事情发生"。

7eumitmz

7eumitmz2#

如果你已经有了一个onChange处理程序来处理单个的FieldEditors,我不明白为什么你不能把状态上移到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);
vpfxa7rd

vpfxa7rd3#

正如前面的回答所说,尝试将状态移动到顶层组件,并通过传递给其子组件的回调来修改状态。
如果您确实需要访问一个声明为功能组件(钩子)的子状态,您可以在父组件中声明一个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();

0g0grzrc

0g0grzrc4#

现在是2020年,你们中的许多人会来到这里寻找类似的解决方案,但要使用Hooks(它们很棒!)以及代码清洁度和语法方面的最新方法。
因此,正如前面的答案所述,解决这类问题的最佳方法是在子组件fieldEditor之外保存状态,您可以通过多种方式来实现这一点。
最“复杂”的是父节点和子节点都可以访问和修改的全局上下文(状态)。当组件在树层次结构中很深时,这是一个很好的解决方案,因此在每一层中发送 prop 的成本很高。
在这种情况下,我认为这是不值得的,一个更简单的方法将带给我们想要的结果,只是使用强大的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是调用时指定的值(本例中为空对象),位置1是对修改该值的函数的引用。
现在有了子组件FieldEditor,你甚至不需要创建一个带return语句的函数,一个带arrow函数的lean常量就可以了!

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

我们已经完成了,没有别的了。仅仅有了这两个功能强大的组件,我们就有了最终目标“访问”我们的子FieldEditor值,并在我们的父FieldEditor值中展示它。
您可以查看5年前的公认答案,看看Hooks是如何使React代码更精简的(精简了很多!)。
希望我的回答能帮助你学习和理解更多关于钩子的知识,如果你想检查一个working example here it is

9gm1akwq

9gm1akwq5#

现在您可以访问InputField的状态,它是FormEditor的子级。
基本上,只要输入字段(子字段)的状态发生变化,我们就从事件对象中获取值,然后将该值传递给父字段,在父字段中设置状态。
单击按钮时,我们只是打印 input 字段的状态。
这里的关键点是,我们使用props来获取 *input字段的 * id/value,并在生成可重用的子 input 字段时调用在 *input字段 * 上设置为属性的函数。

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

esbemjvw6#

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

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

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

相关问题