Reactで子供の状態にアクセスする方法は?


214

私は次の構造を持っています:

FormEditor-複数のFieldEditorを保持 FieldEditor-フォームのフィールドを編集し、その状態に関するさまざまな値を保存します

FormEditor内でボタンをクリックすると、すべてのFieldEditorコンポーネントのフィールドに関する情報、その状態の情報を収集し、FormEditor内にすべて表示できるようにしたいと考えています。

私は、FieldEditorの状態以外のフィールドに関する情報を格納し、FormEditor代わりにの状態にすることを検討しました。ただし、情報を変更して状態に格納するときに、FormEditorFieldEditorコンポーネントをリッスンする必要があります。

代わりに、子供の状態にアクセスすることはできませんか?理想ですか?


4
「代わりに子供たちの州だけにアクセスすることはできませんか?それは理想的ですか?」いいえ。状態は内部のものであり、外部に漏出してはなりません。コンポーネントのアクセサメソッドを実装することもできますが、それでも理想的ではありません。
Felix Kling、2015年

@FelixKling次に、子供から親へのコミュニケーションの理想的な方法はイベントのみであることを示唆していますか?
Moshe Revah

3
はい、イベントは一方通行です。または、Fluxが促進するような一方向のデータフローを持っている:facebook.github.io/flux
Felix Kling

1
FieldEditor個別に使用しない場合は、状態を保存してFormEditorおくとよいでしょう。この場合、FieldEditorインスタンスはprops、フォームエディターではなく、渡されたフォームエディターに基づいてレンダリングされますstate。より複雑ですが柔軟な方法は、任意のコンテナーの子を通過してFormEditorその中のすべてのインスタンスを見つけ、それらをJSONオブジェクトにシリアル化するシリアライザーを作成することです。JSONオブジェクトは、フォームエディターでのインスタンスのネストレベルに基づいて、オプションでネストできます(複数のレベル)。
Meglio 2016年

4
私は、React Docsの「リフティングステートアップ」がおそらくこれを行うための最も「Reacty」な方法だと思います
icc97

回答:


135

個々のFieldEditorのonChangeハンドラーがすでにある場合、状態をFormEditorコンポーネントに移動して、そこから親の状態を更新するFieldEditorsにコールバックを渡すことができない理由はわかりません。それは、私にはそれを行うためのよりReact-yの方法のようです。

おそらくこれに沿った何か:

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
これはonChangeには役立ちますが、onSubmitではそれほど役立ちません。
190290000ルーブルマン2017

1
@ 190290000RubleMan意味がわかりませんが、個々のフィールドのonChangeハンドラーにより、FormEditorコンポーネントの状態で常に完全なフォームデータを利用できるため、onSubmitにはのようなものが含まれますmyAPI.SaveStuff(this.state);。もう少し詳しく説明すると、多分私はあなたにより良い答えを与えることができます:)
Markus-ipse

異なるタイプの3つのフィールドを持つフォームがあり、それぞれに独自の検証があります。提出する前に、それぞれを検証したいのですが。検証が失敗した場合、エラーのある各フィールドが再レンダリングされます。私はあなたの提案された解決策でこれを行う方法を理解していませんが、おそらく方法があります!
190290000ルーブルマン2017

1
@ 190290000RubleMan元の質問とは別のケースのようです。詳細とおそらくいくつかのサンプルコードを使用して新しい質問を開きます。後で確認できます:)
Markus-ipse

この答えは役に立つかもしれません:状態フックを使用し、状態を作成する場所、すべてのキーストロークでフォームの再レンダリングを回避する方法、フィールド検証を行う方法などをカバーしています
Andrew

189

子コンポーネントの状態にアクセスする方法について詳しく説明する直前に、この特定のシナリオを処理するためのより良いソリューションに関するMarkus-ipseの回答を必ず読んでください。

実際にコンポーネントの子の状態にアクセスしたい場合は、呼び出されるプロパティrefを各子に割り当てることができます。参照を実装する方法は2つ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して状態を取得します。

注意すべきことの1つは、子コンポーネントにアクセスする前に、子コンポーネントがレンダリングされていることを確認してください^ _ ^

あなたの代わりにコンポーネントのDOMノード上でこれらの参照を使用している場合、上記のように、(例えば<div ref={(divRef) => {this.myDiv = divRef;}} />)その後、this.divRefコンポーネントインスタンスの代わりに、基本となるDOM要素を返します。

さらに詳しい情報

Reactのrefプロパティの詳細については、Facebookのこのページをご覧ください

「物事を起こす」ために子を使用してはならないことを述べている「Do n't Overuse Refs」セクションを必ずお読みくださいstate

これが^ _ ^お役に立てば幸いです

編集:React.createRef()参照を作成するためのメソッドが追加されました。ES5コードを削除しました。


5
また、少し整頓された、あなたはから関数を呼び出すことができますthis.refs.yourComponentNameHere。関数を使用して状態を変更するのに便利です。例: this.refs.textInputField.clearInput();
ディランピアース

7
注意(ドキュメントから):Refは、ストリーミングReactive propsおよびを介して行うのが不便な方法で、特定の子インスタンスにメッセージを送信するための優れた方法ですstate。ただし、それらは、アプリケーションを介してデータをフローするための必須の抽象化ではありません。デフォルトでは、Reactiveデータフローrefを使用し、本質的に非Reactive であるユースケースのためにを保存します。
リン

2
コンストラクター内で定義された状態のみを取得します(最新の状態ではありません)
VladoPandžić16年

3
現在、参照の使用は、「制御されていないコンポーネント」の使用に分類され、「制御されたコンポーネント」の使用が推奨されています
icc97

子コンポーネントがRedux connectedコンポーネントの場合、これを行うことはできますか?
jmancherje 2018年

6

その2020年と多くの皆さんがここに来て、同様のソリューションを探しますが、フック(それらは素晴らしいです!)と、コードのクリーンさと構文の観点から最新のアプローチを使用します。

したがって、以前の回答で述べたように、この種の問題への最良のアプローチは、子コンポーネントの外側で状態を保持することです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が呼び出し時に指定された値(この場合は空のオブジェクト)であり、位置1が値を変更する関数への参照である配列を返すことに注意してください。

子コンポーネントFieldEditorを使用すると、returnステートメントを使用して関数を作成する必要さえありません。矢印関数を使用した無駄のない定数で実行できます。

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

Aaaaand完了しました。これ以上はありません。これら2つのスライム機能コンポーネントだけで、最終的な目標は子のFieldEditor値に「アクセス」し、親でそれを披露することです。

5年前から受け入れられた回答をチェックして、フックがどのようにしてReactコードをより効率的にしたかを確認できます(多くの場合!)。

私の回答がフックについての理解と理解に役立つことを願っています。実際の例をここで確認したい場合は、こちらをご覧ください。


4

これで、FormEditorの子であるInputFieldの状態にアクセスできます。

基本的に、入力フィールド(子)の状態が変化するたびに、イベントオブジェクトから値を取得し、この値を、Parentの状態が設定されているParentに渡します。

ボタンをクリックすると、入力フィールドの状態が出力されます。

ここで重要なのは、propsを使用して入力フィールドのID /値を取得し、再利用可能な子入力フィールドを生成するときに、入力フィールドの属性として設定されている関数を呼び出すことです。

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

7
これに賛成できるかどうかわからない。@ Markus-ipseでまだ回答されていない、あなたはここで何を述べますか?
アレクサンダーバード

3

前の回答で述べたように、状態を最上位のコンポーネントに移動し、その子に渡されるコールバックを通じて状態を変更してみてください。

機能コンポーネント(フック)として宣言されている子の状態に本当にアクセスする必要がある場合は、親コンポーネントで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();

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.