フォーム要素の状態を兄弟/親要素に渡す正しい方法は何ですか?


186
  • 2つの子クラスC1とC2をレンダリングするReactクラスPがあるとします。
  • C1には入力フィールドが含まれています。この入力フィールドをFooと呼びます。
  • 私の目標は、C2がFooの変更に反応できるようにすることです。

私は2つの解決策を考え出しましたが、どちらも正しくないと感じています。

最初の解決策:

  1. 割り当てP状態、state.input
  2. onChangePで関数を作成しますstate.input。これは、イベントを受け取り、を設定します。
  3. これを渡すonChangeようにC1 props、およびC1が結合してみましょうthis.props.onChangeonChangeはFooの。

これは機能します。Fooの値が変化するたびに、setStatePでa がトリガーされるため、PにはC2に渡す入力があります。

しかし、それは同じ理由でまったく適切とは言えません。私は、子要素から親要素の状態を設定しています。これは、Reactの設計原則、つまり単一方向のデータフローを裏切るようです。
これは私がそうすることになっている方法ですか、それとももっとリアクトナチュラルな解決策がありますか?

2番目の解決策:

FooをPに入れるだけです。

しかし、これは、アプリを構造化するときに従うべき設計原則renderです。つまり、すべてのフォーム要素を最上位クラスのに入れますか?

私の例のように、C1の大きなレンダリングがある場合render、C1にrenderフォーム要素があるため、C1 全体をP に配置したくありません。

どうすればよいですか?


私はまったく同じことをやろうとしています。正しく機能しているにもかかわらず、それは巨大なハックに過ぎないと感じています
Mirko

回答:


198

それで、私があなたを正しく理解しているなら、あなたの最初の解決策はルートコンポーネントの状態を維持していることを示唆していますか?私はReactのクリエイターのために話すことはできませんが、一般的に、これは適切な解決策であると思います。

状態を維持することは、Reactが作成された理由の少なくとも1つです(少なくとも私はそう思います)。相互に依存する可動部分が多い動的UIを処理するために独自の状態パターンのクライアント側を実装したことがある場合は、この状態管理の苦痛の多くを軽減するので、Reactが気に入るはずです。

状態を階層の上位に維持し、イベントによって更新することにより、データフローはほぼ単方向であり、ルートコンポーネントのイベントに応答するだけであり、双方向のバインディングを介して実際にデータを取得するわけではありません。ルートコンポーネントに「ちょっと、ここで何かが発生しました。値を確認してください」と伝えるか、状態を更新するために子コンポーネントの一部のデータの状態を渡します。C1で状態を変更し、C2にそれを認識させたいので、ルートコンポーネントで状態を更新して再レンダリングすることにより、ルートコンポーネントで状態が更新されて渡されたので、C2のプロップが同期されます。 。

class Example extends React.Component {
  constructor (props) {
    super(props)
    this.state = { data: 'test' }
  }
  render () {
    return (
      <div>
        <C1 onUpdate={this.onUpdate.bind(this)}/>
        <C2 data={this.state.data}/>
      </div>
    )
  }
  onUpdate (data) { this.setState({ data }) }
}

class C1 extends React.Component {
    render () {
      return (
        <div>
          <input type='text' ref='myInput'/>
          <input type='button' onClick={this.update.bind(this)} value='Update C2'/>
        </div>
      )
    }
    update () {
      this.props.onUpdate(this.refs.myInput.getDOMNode().value)
    }
})

class C2 extends React.Component {
    render () {
      return <div>{this.props.data}</div>
    }
})

ReactDOM.renderComponent(<Example/>, document.body)

5
問題ない。私は実際にこの投稿を書いた後に戻っていくつかのドキュメントを読み直しましたが、彼らの考えやベストプラクティスと一致しているようです。Reactには本当に優れたドキュメントがあり、どこに行けばいいのか迷うたびに、ドキュメントのどこかでカバーされています。こちらの状態に関するセクション、facebook.github.io
react / docs /…を

@captrayですC2が、getInitialStatefor dataがあり、その内部renderで使用されている場合はthis.state.dataどうですか?
Dmitry Polushkin 14

2
@DmitryPolushkin私があなたの質問を正しく理解している場合、ルートコンポーネントからC2に小道具としてデータを渡す必要があります。C2では、そのデータは初期状態として設定されます(つまり、getInitialState:function(){return {someData:this.props.dataFromParentThatWillChange}}。componentWillReceivePropsを実装し、this.setStateを呼び出して新しいプロパティを更新し、 C2での状態私が最初に答えたので、私はフラックスを使用してきた、と非常にあなたにもそれを見ていることをお勧めしますそれはあなたのコンポーネントクリーナーを行い、あなたが状態を考える方法が変更されます。。。
captray

3
@DmitryPolushkinフォローアップに投稿したかったです。facebook.github.io/react/tips/…何をしているのかがわかっていて、データが変化することを知っていれば問題ありませんが、多くの場合、移動することができます。これを階層として構築する必要がないことに注意することも重要です。C1とC2をDOMの異なる場所にマウントすることができ、どちらも一部のデータの変更イベントをリッスンできます。多くの人が、必要のない階層コンポーネントを要求しているのを目にします。
キャプトレイ2014

5
上記のコードには2つのバグがあります-どちらも正しいコンテキストで「this」をバインドしないことを含みます。私は上記の修正を行いました。また、codepenのデモが必要な人のために修正しました:codepen.io/anon/pen/wJrRpZ?editors=0011
Alex H

34

今、Reactを使用してアプリを作成したので、半年前に尋ねたこの質問についていくつかの考えを共有したいと思います。

読むことをお勧めします

最初の投稿は、Reactアプリをどのように構成すべきかを理解するのに非常に役立ちます。

Fluxは、なぜ Reactアプリをこのように構造化する必要があるのという質問に答えます(構造化する方法ではなく)。Reactはシステムの50%にすぎません。Fluxを使用すると、全体像を把握し、それらが一貫したシステムを構成する方法を確認できます。

質問に戻ります。

私の最初の解決策に関しては、データがまだ単一方向に進んでいるので、ハンドラーを逆方向に動かしてもまったく問題ありません。

ただし、ハンドラーにPのsetStateをトリガーさせるかどうかは、状況によって異なります。

アプリが単純なMarkdownコンバーターであり、C1が生の入力で、C2がHTML出力である場合、C1にPのsetStateをトリガーさせても問題ありませんが、これは推奨される方法ではないと主張する人もいます。

しかし、アプリはToDoリストであれば、C1は、HTMLでのToDoリスト、C2を新たにTODOを作成するための入力されて、あなたはおそらく、P 2つのよりレベルアップして行くために、ハンドラにしたい-とdispatcherさせており、store更新をdata store、次に、データをPに送信し、ビューにデータを入力します。そのFluxの記事を参照してください。次に例を示します。Flux-TodoMVC

一般的に、私はtodoリストの例で説明されている方法を好みます。アプリの状態が少ないほど良いです。


7
これについては、ReactとFluxの両方のプレゼンテーションでよく話します。私が強調しようとする点は、あなたが上で説明したものであり、それはビューステートとアプリケーションステートの分離です。UIの状態(プリセット値など)を保存している場合は特に、ビューステートからアプリケーションステートに移行できる状況があります。1年後に自分の考えを持ち帰ったのは素晴らしいことだと思います。+1
キャプトレイ2015

@captrayでは、reduxはあらゆる状況で反応するよりも強力だと言えるでしょうか。彼らの学習曲線にもかかわらず...(Javaから)
Bruce Sun

どういう意味かわかりません。それらは2つの異なることを非常にうまく処理し、懸念を適切に分離します。
キャプトレイ、2017年

1
reduxを使用するプロジェクトがあり、その数か月後には、すべてに対してアクションが使用されているように見えます。それは、スパゲッティコードのこの大きな組み合わせのようなものであり、理解することは不可能であり、完全な災害です。状態管理は良いかもしれませんが、ひどく誤解されているかもしれません。flux / reduxは、グローバルにアクセスする必要がある状態の部分にのみ使用する必要があると想定しても安全ですか?
wayofthefuture 2017

1
@wayofthefutureあなたのコードを見なくても、良いole spaghetti jQueryのようにスパゲッティReactがたくさん表示されていると言えるでしょう。私が提供できる最高のアドバイスは、SRPを試し、それに従うことです。コンポーネントをできるだけシンプルにします。可能であれば、レンダリングコンポーネントを簡素化します。また、<DataProvider />コンポーネントのような抽象化も要求します。それは一つのことをうまくやっています。データを提供します。これは通常「ルート」コンポーネントになり、子(および複製)を小道具(定義済みコントラクト)として活用することによってデータを渡します。最終的には、最初にサーバーについて考えてみてください。それはあなたのJSをより良いデータ構造ではるかにきれいにします。
キャプトレイ2017

5

第1の解決策は、と親コンポーネントの状態を保ち、ある正しいです。しかし、より複雑な問題のために、あなたはいくつか考えなければならない状態管理ライブラリreduxが反応で使用される最も人気の一つです。


同意した。ほとんどの人が「Pure React」で物事を書いていたときに、私の返答が書き戻されました。フラックス爆発の前。
キャプトレイ2018年

5

5年後、React Hooksの導入により、useContextフックを使用してそれを行うよりエレガントな方法が存在するようになりました。

グローバルスコープでコンテキストを定義し、親コンポーネントで変数、オブジェクト、関数をエクスポートしてから、提供されたコンテキストでアプリの子をラップし、子コンポーネントで必要なものをインポートします。以下は概念実証です。

import React, { useState, useContext } from "react";
import ReactDOM from "react-dom";
import styles from "./styles.css";

// Create context container in a global scope so it can be visible by every component
const ContextContainer = React.createContext(null);

const initialAppState = {
  selected: "Nothing"
};

function App() {
  // The app has a state variable and update handler
  const [appState, updateAppState] = useState(initialAppState);

  return (
    <div>
      <h1>Passing state between components</h1>

      {/* 
          This is a context provider. We wrap in it any children that might want to access
          App's variables.
          In 'value' you can pass as many objects, functions as you want. 
           We wanna share appState and its handler with child components,           
       */}
      <ContextContainer.Provider value={{ appState, updateAppState }}>
        {/* Here we load some child components */}
        <Book title="GoT" price="10" />
        <DebugNotice />
      </ContextContainer.Provider>
    </div>
  );
}

// Child component Book
function Book(props) {
  // Inside the child component you can import whatever the context provider allows.
  // Earlier we passed value={{ appState, updateAppState }}
  // In this child we need the appState and the update handler
  const { appState, updateAppState } = useContext(ContextContainer);

  function handleCommentChange(e) {
    //Here on button click we call updateAppState as we would normally do in the App
    // It adds/updates comment property with input value to the appState
    updateAppState({ ...appState, comment: e.target.value });
  }

  return (
    <div className="book">
      <h2>{props.title}</h2>
      <p>${props.price}</p>
      <input
        type="text"
        //Controlled Component. Value is reverse vound the value of the variable in state
        value={appState.comment}
        onChange={handleCommentChange}
      />
      <br />
      <button
        type="button"
        // Here on button click we call updateAppState as we would normally do in the app
        onClick={() => updateAppState({ ...appState, selected: props.title })}
      >
        Select This Book
      </button>
    </div>
  );
}

// Just another child component
function DebugNotice() {
  // Inside the child component you can import whatever the context provider allows.
  // Earlier we passed value={{ appState, updateAppState }}
  // but in this child we only need the appState to display its value
  const { appState } = useContext(ContextContainer);

  /* Here we pretty print the current state of the appState  */
  return (
    <div className="state">
      <h2>appState</h2>
      <pre>{JSON.stringify(appState, null, 2)}</pre>
    </div>
  );
}

const rootElement = document.body;
ReactDOM.render(<App />, rootElement);

この例は、コードサンドボックスエディターで実行できます。

コンテキスト付きの通過状態を編集


2

私が書いている現時点では、わかりやすい慣用的なReactソリューションでは答えがないことに驚いています。だからここにある(サイズと複雑さを他のものと比較する):

class P extends React.Component {
    state = { foo : "" };

    render(){
        const { foo } = this.state;

        return (
            <div>
                <C1 value={ foo } onChange={ x => this.setState({ foo : x })} />
                <C2 value={ foo } />
            </div>
        )
    }
}

const C1 = ({ value, onChange }) => (
    <input type="text"
           value={ value }
           onChange={ e => onChange( e.target.value ) } />
);

const C2 = ({ value }) => (
    <div>Reacting on value change: { value }</div>
);

子要素から親要素の状態を設定しています。これは、Reactの設計原則、つまり単一方向のデータフローを裏切るようです。

任意の制御input(リアクトのフォームでの作業の慣用方法が)その中で親状態を更新しますonChange、コールバック、まだ何も裏切るません。

たとえば、C1コンポーネントをよく見てください。C1組み込みinputコンポーネントが状態の変化を処理する方法に大きな違いはありますか?ないからです。状態を持ち上げ、値とonChangeのペアを渡すことは、生のReactの場合は慣用的です。いくつかの回答が示唆するように、参照の使用ではありません。


1
どのバージョンの反応を使用していますか?実験的なクラスプロパティの問題が発生し、fooが定義されていません。
Isaac Pak

それはreactのバージョンに関するものではありませんでした。コンポーネントのコードが間違っていました。修正しました。今すぐお試しください。
ギャパートン

明らかに、から状態メンバーを抽出this.stateする必要があり、レンダリングでは戻り値が欠落しており、複数のコンポーネントをdivまたは何かでラップする必要があります。元の答えを書いたとき、どうしてそれを逃したのかわかりません。編集ミスだったに違いありません。
ギャパートン

1
私はこのソリューションが好きです。誰かがそれをいじくり回したいなら、ここにあなたのためのサンドボックスがあります。
Isaac Pak

2

より最近の回答例を使用して、 React.useState

親コンポーネントで状態を維持することをお勧めします。親は、2つの子コンポーネント間で管理するため、それにアクセスできる必要があります。Reduxが管理するようなグローバル状態に移行することは、ソフトウェアエンジニアリングにおいてグローバル変数がローカルよりも一般的に悪いのと同じ理由で推奨されません

状態が親コンポーネント内にある場合、親が子valueonChangeハンドラーをプロップで指定すると、子は状態を変更できます値リンクまたは状態リンクパターンと呼ばれることもあります)。ここでは、フックを使用してそれを行う方法を示します。


function Parent() {
    var [state, setState] = React.useState('initial input value');
    return <>
        <Child1 value={state} onChange={(v) => setState(v)} />
        <Child2 value={state}>
    </>
}

function Child1(props) {
    return <input
        value={props.value}
        onChange={e => props.onChange(e.target.value)}
    />
}

function Child2(props) {
    return <p>Content of the state {props.value}</p>
}

親コンポーネント全体が子の入力変更時に再レンダリングされます。これは、親コンポーネントが小さい/再レンダリングが速い場合は問題にならない可能性があります。親コンポーネントの再レンダリングのパフォーマンスは、一般的なケース(大きなフォームなど)でも問題になる可能性があります。これはあなたのケースで解決された問題です(以下を参照)。

状態リンクパターン親の再レンダリングなしは、Hookstateのようなサードパーティライブラリを使用して実装する方が簡単です -スーパーチャージReact.useStateあなたのものを含め、ユースケースのカバー様々な。(免責事項:私はプロジェクトの作成者です)。

これがフックステートでどのように見えるかです。Child1入力を変更し、Child2それに反応します。Parentのみ、状態を保持しますが、状態の変化に再表示されませんChild1Child2なります。

import { useStateLink } from '@hookstate/core';

function Parent() {
    var state = useStateLink('initial input value');
    return <>
        <Child1 state={state} />
        <Child2 state={state}>
    </>
}

function Child1(props) {
    // to avoid parent re-render use local state,
    // could use `props.state` instead of `state` below instead
    var state = useStateLink(props.state)
    return <input
        value={state.get()}
        onChange={e => state.set(e.target.value)}
    />
}

function Child2(props) {
    // to avoid parent re-render use local state,
    // could use `props.state` instead of `state` below instead
    var state = useStateLink(props.state)
    return <p>Content of the state {state.get()}</p>
}

PS:深くネストされたデータ、状態の検証、フックを使用したグローバルな状態など、類似したより複雑なシナリオをカバーするが他にも多数ありますsetState。フック状態と上記で説明した手法を使用する完全なサンプルアプリケーションオンラインもあります


1

ReduxとReactReduxライブラリを学ぶ必要があります。1つのストアで状態と小道具を構造化し、後でコンポーネントでそれらにアクセスできます。


1

React> = 16.3では、refとforwardRefを使用して、親から子のDOMにアクセスできます。もう古い方法の参照を使用しないでください。
ここにあなたのケースを使用した例があります:

import React, { Component } from 'react';

export default class P extends React.Component {
   constructor (props) {
      super(props)
      this.state = {data: 'test' }
      this.onUpdate = this.onUpdate.bind(this)
      this.ref = React.createRef();
   }

   onUpdate(data) {
      this.setState({data : this.ref.current.value}) 
   }

   render () {
      return (
        <div>
           <C1 ref={this.ref} onUpdate={this.onUpdate}/>
           <C2 data={this.state.data}/>
        </div>
      )
   }
}

const C1 = React.forwardRef((props, ref) => (
    <div>
        <input type='text' ref={ref} onChange={props.onUpdate} />
    </div>
));

class C2 extends React.Component {
    render () {
       return <div>C2 reacts : {this.props.data}</div>
    }
}

参照してください。参考文献ForwardRefをレフリーとforwardRefに関する詳細な情報については。


0
  1. 正しいことは、親コンポーネントに状態を持たせることです参照を避け、何をしないかです
  2. 問題は、常にすべての子を更新しないことです、フィールドに入力するときに
  3. したがって、各子は(PureComponentではなく)コンポーネントであり、実装する必要があります。 shouldComponentUpdate(nextProps, nextState)
  4. このように、フォームフィールドに入力すると、そのフィールドのみが更新されます

以下のコードは、BabelJS 6のES.Next@boundアノテーションとclass-propertiesを使用しています(このアノテーションは、bindと同様のメンバー関数にこの値を設定します)。 babel-plugin-transform-decorators-legacy

/*
© 2017-present Harald Rudell <harald.rudell@gmail.com> (http://www.haraldrudell.com)
All rights reserved.
*/
import React, {Component} from 'react'
import {bound} from 'class-bind'

const m = 'Form'

export default class Parent extends Component {
  state = {one: 'One', two: 'Two'}

  @bound submit(e) {
    e.preventDefault()
    const values = {...this.state}
    console.log(`${m}.submit:`, values)
  }

  @bound fieldUpdate({name, value}) {
    this.setState({[name]: value})
  }

  render() {
    console.log(`${m}.render`)
    const {state, fieldUpdate, submit} = this
    const p = {fieldUpdate}
    return (
      <form onSubmit={submit}> {/* loop removed for clarity */}
        <Child name='one' value={state.one} {...p} />
        <Child name='two' value={state.two} {...p} />
        <input type="submit" />
      </form>
    )
  }
}

class Child extends Component {
  value = this.props.value

  @bound update(e) {
    const {value} = e.target
    const {name, fieldUpdate} = this.props
    fieldUpdate({name, value})
  }

  shouldComponentUpdate(nextProps) {
    const {value} = nextProps
    const doRender = value !== this.value
    if (doRender) this.value = value
    return doRender
  }

  render() {
    console.log(`Child${this.props.name}.render`)
    const {value} = this.props
    const p = {value}
    return <input {...p} onChange={this.update} />
  }
}

0

親から子へ、またはその逆でデータを渡す概念について説明します。

import React, { Component } from "react";
import ReactDOM from "react-dom";

// taken refrence from https://gist.github.com/sebkouba/a5ac75153ef8d8827b98

//example to show how to send value between parent and child

//  props is the data which is passed to the child component from the parent component

class Parent extends Component {
  constructor(props) {
    super(props);

    this.state = {
      fieldVal: ""
    };
  }

  onUpdateParent = val => {
    this.setState({
      fieldVal: val
    });
  };

  render() {
    return (
      // To achieve the child-parent communication, we can send a function
      // as a Prop to the child component. This function should do whatever
      // it needs to in the component e.g change the state of some property.
      //we are passing the function onUpdateParent to the child
      <div>
        <h2>Parent</h2>
        Value in Parent Component State: {this.state.fieldVal}
        <br />
        <Child onUpdate={this.onUpdateParent} />
        <br />
        <OtherChild passedVal={this.state.fieldVal} />
      </div>
    );
  }
}

class Child extends Component {
  constructor(props) {
    super(props);

    this.state = {
      fieldValChild: ""
    };
  }

  updateValues = e => {
    console.log(e.target.value);
    this.props.onUpdate(e.target.value);
    // onUpdateParent would be passed here and would result
    // into onUpdateParent(e.target.value) as it will replace this.props.onUpdate
    //with itself.
    this.setState({ fieldValChild: e.target.value });
  };

  render() {
    return (
      <div>
        <h4>Child</h4>
        <input
          type="text"
          placeholder="type here"
          onChange={this.updateValues}
          value={this.state.fieldVal}
        />
      </div>
    );
  }
}

class OtherChild extends Component {
  render() {
    return (
      <div>
        <h4>OtherChild</h4>
        Value in OtherChild Props: {this.props.passedVal}
        <h5>
          the child can directly get the passed value from parent by this.props{" "}
        </h5>
      </div>
    );
  }
}

ReactDOM.render(<Parent />, document.getElementById("root"));


1
前の回答の所有者にコメントとして通知し、回答で提供された説明を追加することをお勧めします。続いて礼儀のためにあなたの名前を振り返ります。
Thomas Easo

@ThomasEaso私はここで彼を見つけることができません、私はそれをチェックしました。その他の提案はありますか
akshay

彼のアイコンの左側にある「編集」ボタンをクリックし、彼の回答を変更して送信します。それはモデレーターに行き、彼らはあなたの貴重なコメントのために必要なことをします。
Thomas Easo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.