ReactJS状態vsプロップ


121

これは、回答可能と独断の境界線をたどっているかもしれませんが、複雑さが増し、何らかの方向性を使用する可能性がある場合に、ReactJSコンポーネントをどのように構成するかについて、前後に説明します。

AngularJSから来て、モデルをプロパティとしてコンポーネントに渡し、コンポーネントにモデルを直接変更させたいと思います。または、モデルをさまざまなstateプロパティに分割し、上流に送り返すときに一緒にコンパイルする必要がありますか?ReactJSの方法は何ですか?

ブログ投稿エディターの例を見てみましょう。モデルを直接変更しようとすると、次のようになります。

var PostEditor = React.createClass({
  updateText: function(e) {
    var text = e.target.value;
    this.props.post.text = text;
    this.forceUpdate();
  },
  render: function() {
    return (
      <input value={this.props.post.text} onChange={this.updateText}/>
      <button onClick={this.props.post.save}/>Save</button>
    );
  }
});

それは間違っているようです。

次のように保存する前に、モデルtextプロパティstateを作成し、モデルにコンパイルするためのReactの方法ですか?

var PostEditor = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },
  componentWillMount: function() {
    this.setState({
      text: this.props.post.text
    });
  },
  updateText: function(e) {
    this.setState({
      text: e.target.value
    });
  },
  savePost: function() {
    this.props.post.text = this.state.text;
    this.props.post.save();
  },
  render: function() {
    return (
      <input value={this.state.text} onChange={this.updateText}/>
      <button onClick={this.savePost}/>Save</button>
    );
  }
});

これにはを呼び出す必要はありませんがthis.forceUpdate()、モデルが大きくなると(投稿に作成者、件名、タグ、コメント、評価などが含まれる場合があります)、コンポーネントは非常に複雑になり始めます。

ReactLinkでの最初の方法は行く方法ですか?

回答:


64

あなたの2番目のアプローチはもっとそれに似ています。Reactは、とモデルがアプリをどのように流れるかを考慮しているため、モデルをあまり考慮していません。理想的には、投稿モデルはルートの単一のコンポーネントに格納されます。次に、それぞれがモデルの一部を使用する子コンポーネントを作成します。

データを変更する必要がある子にコールバックを渡し、子コンポーネントからそれらを呼び出すことができます。

this.propsまたはthis.stateを直接変更することはお勧めできません。Reactが変更を取得できないためです。これは、Reactがポストプロップを浅く比較して、変更されたかどうかを判断するためです。

このjsfiddleを作成して、データが外部コンポーネントから内部コンポーネントにどのように流れるかを示しました。

このhandleClickメソッドは、状態を(不適切に)更新する3つの方法を示しています。

var Outer = React.createClass({

  getInitialState: function() {
    return {data: {value: 'at first, it works'}};
  },

  handleClick: function () {

    // 1. This doesn't work, render is not triggered.
    // Never set state directly because the updated values
    // can still be read, which can lead to unexpected behavior.

    this.state.data.value = 'but React will never know!';

    // 2. This works, because we use setState

    var newData = {value: 'it works 2'};
    this.setState({data: newData});

    // 3. Alternatively you can use React's immutability helpers
    // to update more complex models.
    // Read more: http://facebook.github.io/react/docs/update.html

    var newState = React.addons.update(this.state, {
      data: {value: {$set: 'it works'}}
    });
    this.setState(newState);
 },

  render: function() {
      return <Inner data={this.state.data} handleClick={this.handleClick} />;
  }
});

しかし、独自の状態操作関数を備えた不透明なモデルがある場合はどうすればよいでしょうか。たとえば、textフィールドの代わりに、setText 検証などを行うメソッドがあるとします。メソッド(2)setTextが純粋で、モデルの新しいインスタンスを返す場合に機能することがわかります。ただし、setText内部状態を更新するだけの場合はforceUpdate、を呼び出す必要がありますよね?
hugomg 2014年

1
はい、あなたはを呼び出すことができますforceUpdateが、その時点でReactから「漏れ」ています。setState()再レンダリングを手動でトリガーする必要がないように、不透明モデルにコールバックを渡す方がよい場合があります。
jxg 2014年

完全に理解できているかどうかはまだわかりません。では、データを変更することを目的としたコンポーネントは、渡された小道具の詳細なコピーを実行する必要がありますか?次に、元のデータを変更しないように、そのコピーを変更してアップストリームに送信しますか?最終的に、変更はルートに流れ、そこで処理され、アプリ全体が再レンダリングされますか?そうですか?
ニコラス2014年

97

2016年の更新: Reactが変更され、「props vs state」の説明が非常にシンプルになりました。コンポーネントがデータを変更する必要がある場合-それを状態にし、そうでない場合は小道具にします。小道具は現在読み取り専用なので。

小道具と状態の正確な違いは何ですか?

ここで良い説明を見つけることができます(フルバージョン)

小道具と状態の変更


1
実際にsetProps()はコンポーネント内の小道具を変更し、再レンダリングをトリガーする可能性があります
WaiKit Kung

2
setPropsは非推奨であり、使用しないでください。置き換えは、コンポーネントを再レンダリングして、Reactに違いを処理させることです。
jdmichal 16

また、説明ビデオを探している場合:youtube.com/watch
v

35

React docから

小道具は不変です。それらは親から渡され、親によって「所有」されます。相互作用を実装するために、コンポーネントに変更可能な状態を導入します。this.stateはコンポーネントにプライベートであり、this.setState()を呼び出すことで変更できます。状態が更新されると、コンポーネントは自身を再レンダリングします。

TrySpaceから:プロップ(または状態)が(setProps / setStateまたは親を介して)更新されると、コンポーネントも再レンダリングされます。


16

Thinking in Reactの読み:

それぞれを調べて、どれが状態かを調べましょう。各データについて3つの質問をするだけです。

  1. 親から小道具を介して渡されますか?もしそうなら、それはおそらく状態ではありません。
  2. 時間の経過とともに変化しますか?そうでない場合は、おそらく状態ではありません。

  3. コンポーネント内の他の状態または小道具に基づいてそれを計算できますか?もしそうなら、それは状態ではありません。


13

私があなたの質問に答えているかどうかはわかりませんが、特に大規模/成長中のアプリケーションでは、コンテナ/コンポーネントパターンが非常にうまく機能していることがわかりました。

基本的に、2つのReactコンポーネントがあります。

  • 「純粋な」表示コンポーネント。スタイリングとDOMの相互作用を扱います。
  • 外部データへのアクセス/保存、状態の管理、および表示コンポーネントのレンダリングを処理するコンテナコンポーネント。

注:この例は、このような単純なケースでは非常に冗長であるため、おそらくこのパターンの利点を説明するには単純すぎます。

/**
 * Container Component
 *
 *  - Manages component state
 *  - Does plumbing of data fetching/saving
 */

var PostEditorContainer = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },

  componentWillMount: function() {
    this.setState({
      text: getPostText()
    });
  },

  updateText: function(text) {
    this.setState({
      text: text
    });
  },

  savePost: function() {
    savePostText(this.state.text);
  },

  render: function() {
    return (
      <PostEditor
        text={this.state.text}
        onChange={this.updateText.bind(this)}
        onSave={this.savePost.bind(this)}
      />
    );
  }
});


/**
 * Pure Display Component
 *
 *  - Calculates styling based on passed properties
 *  - Often just a render method
 *  - Uses methods passed in from container to announce changes
 */

var PostEditor = React.createClass({
  render: function() {
    return (
      <div>
        <input type="text" value={this.props.text} onChange={this.props.onChange} />
        <button type="button" onClick={this.props.onSave} />
      </div>
    );
  }
});

利点

表示ロジックとデータ/状態管理を別々に保つことにより、次のような再利用可能な表示コンポーネントが得られます。

  • react-component-playgroundのようなものを使用して、さまざまなプロップのセットで簡単に反復できます
  • 異なる動作の異なるコンテナーでラップすることができます(または他のコンポーネントと組み合わせて、アプリケーションのより大きな部分を構築することができます)

すべての外部通信を処理するコンテナコンポーネントもあります。これにより、後で深刻な変更を加える場合でも、データへのアクセス方法について柔軟に対応できるようになります*。

このパターンにより、単体テストの作成と実装もはるかに簡単になります。

大規模なReactアプリを数回繰り返した結果、特に、計算されたスタイルや複雑なDOMインタラクションを備えた大きなコンポーネントがある場合、このパターンは比較的痛みを伴わないことがわかりました。

*フラックスパターンを読んで、この回答に大きな影響を与えたMarty.jsを 見てください(最近、私は最近たくさん使用しています)Redux(およびreact-redux)。これは、このパターンを非常にうまく実装しています。

2018年以降にこれを読む人への注意:

特にHooksの導入により、この回答が書かれてからReactはかなり進化しました。ただし、この例の基本的な状態管理ロジックは同じままであり、さらに重要なことに、状態ロジックとプレゼンテーションロジックを別々に保つことで得られる利点は、同じ方法で適用されます。


0

Facebookがこのリンクですでに説明したアンチパターンを使用していると思います

ここにあなたが見つけているものがあります:

React.createClass({
  getInitialState: function() {
    return { value: { foo: 'bar' } };
  },

  onClick: function() {
    var value = this.state.value;
    value.foo += 'bar'; // ANTI-PATTERN!
    this.setState({ value: value });
  },

  render: function() {
    return (
      <div>
        <InnerComponent value={this.state.value} />
        <a onClick={this.onClick}>Click me</a>
      </div>
    );
  }
});

内部コンポーネントが初めてレンダリングされるとき、値のプロパティとして{foo: 'bar'}が含まれます。ユーザーがアンカーをクリックすると、親コンポーネントの状態が{value:{foo: 'barbar'}}に更新され、内部コンポーネントの再レンダリングプロセスがトリガーされ、{foo: 'barbar'}が次のように受信されます。プロップの新しい値。

問題は、親コンポーネントと内部コンポーネントが同じオブジェクトへの参照を共有しているため、オブジェクトがonClick関数の2行目で変更されると、内部コンポーネントのプロパティが変更されることです。したがって、再レンダリングプロセスが開始され、shouldComponentUpdateが呼び出されると、this.props.value.fooはnextProps.value.fooと等しくなります。これは、this.props.valueがnextProps.valueと同じオブジェクトを参照しているためです。

その結果、小道具の変更を見逃し、再レンダリングプロセスを短絡させるため、UIは「bar」から「barbar」に更新されません。


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