web-dev-qa-db-ja.com

BackboneでReactを使用する場合、forceUpdate()を回避できますか?

Facebook React奨励 可変(state)状態と不変(props)状態を分離する:

できるだけ多くのコンポーネントをステートレスにしてください。これにより、状態を最も論理的な場所に分離し、冗長性を最小限に抑えて、アプリケーションの推論を容易にします。

状態が変化すると、 setState を呼び出して仮想DOM diffをトリガーします。これにより、必要な場合にのみ実際のDOM更新が行われます。

そこにがあります forceUpdate を呼び出すことでDOM更新を手動でトリガーする方法ですが、 discouraged

通常forceUpdate()のすべての使用を避け、_this.props_および_this.state_からのみ読み取るようにしてくださいrender()。これにより、アプリケーションが非常にシンプルかつ効率的になります。

ただし、私が見たすべてのReact + Backboneの例はこのアドバイスを無視し、モデルとコレクションをpropsに保存し、forceUpdateを呼び出します。

React自身の例でもforceUpdateを使用しています:

しかし、より良い方法はありますか、それはどのような利益をもたらしますか?

40
Dan Abramov

ピートの答えは素晴らしい。

バックボーンモデルは本質的に変異型であり、(それ自体は問題ではありませんが)再レンダリングするときに、比較するモデルの古いバージョンがないことを意味します。これにより、コンポーネントの主要な場所で shouldComponentUpdate メソッドを定義することにより、インテリジェントな最適化が難しくなります。 (また、 元に戻す のような他の理由でモデルの古いバージョンを簡単に保存する機能を失います。)

forceUpdateを呼び出すと、単にshouldComponentUpdateがスキップされ、コンポーネントが強制的に再レン​​ダリングされます。 renderの呼び出しは通常安価であり、Reactはrenderの出力が変更された場合にのみDOMに触れるため、ここでのパフォーマンスの問題はありません。ただし、不変データ(Peteが示唆するようにtoJSON()から生のモデルプロパティオブジェクトを渡すことを含む)を使用する選択肢がある場合は、強くお勧めします。

18
Sophie Alpert

より良い答えが出るまで、 quotePete Hunt 、コアReact開発者:

Backboneモデルの大きなメリットは、データフローを管理してくれたことです。 set()を呼び出すと、データが変更されたことをアプリに通知します。 Reactを使用すると、コールバックおよびReactを介して状態を所有するコンポーネントに通知するだけなので、これはあまり必要ではありません。 _はすべての子が最新であることを保証します。したがって、バックボーンのこの部分はあまり有用ではありません(そして人々はとにかくReactでバックボーンを使用する傾向があります) 。

純粋なJSONを渡す必要はありませんが(私はそうする傾向があり、単純なデータモデルではうまく機能します)、オブジェクトを不変に保つと多くの利点が得られます。

バックボーンモデルでtoJSON()を呼び出すだけで、これを試すことができ、モデルを渡す方法とモデルを渡す方法を確認できます。

(エンファシス鉱山)

興味深いことに、 Backbone.React.ComponenttoJSONを使用する唯一の例ですが、何らかの理由でsetPropsの代わりにsetStatediscouraged too )です。

更新

ピート・ハントのアプローチに基づいて簡単なミックスインを作成しました(setProps、_forceUpdateなし):

_define(function () {

  'use strict';

  var Backbone = require('backbone'),
      _ = require('underscore');

  var BackboneStateMixin = {
    getInitialState: function () {
      return this.getBackboneState(this.props);
    },

    componentDidMount: function () {
      if (!_.isFunction(this.getBackboneState)) {
        throw new Error('You must provide getBackboneState(props).');
      }

      this._bindBackboneEvents(this.props);
    },

    componentWillReceiveProps: function (newProps) {
      this._unbindBackboneEvents();
      this._bindBackboneEvents(newProps);
    },

    componentWillUnmount: function () {
      this._unbindBackboneEvents();
    },

    _updateBackboneState: function () {
      var state = this.getBackboneState(this.props);
      this.setState(state);
    },

    _bindBackboneEvents: function (props) {
      if (!_.isFunction(this.watchBackboneProps)) {
        return;
      }

      if (this._backboneListener) {
        throw new Error('Listener already exists.');
      }

      if (!props) {
        throw new Error('Passed props are empty');
      }

      var listener = _.extend({}, Backbone.Events),
          listenTo = _.partial(listener.listenTo.bind(listener), _, _, this._updateBackboneState);

      this.watchBackboneProps(props, listenTo);
      this._backboneListener = listener;
    },

    _unbindBackboneEvents: function () {
      if (!_.isFunction(this.watchBackboneProps)) {
        return;
      }

      if (!this._backboneListener) {
        throw new Error('Listener does not exist.');
      }

      this._backboneListener.stopListening();
      delete this._backboneListener;
    }
  };

  return BackboneStateMixin;

});
_

どんな種類のモデルやコレクションがあるかは気にしません。

慣習では、Backboneモデルはpropsに入り、そのJSONはmixinによってstateに自動的に配置されます。これを機能させるにはgetBackboneState(props)をオーバーライドする必要があり、オプションでwatchBackbonePropsを新しい値でsetStateを呼び出すタイミングをミックスインに伝える必要があります。

使用例:

_var InfoWidget = React.createClass({
  mixins: [BackboneStateMixin, PopoverMixin],

  propTypes: {
    stampModel: React.PropTypes.instanceOf(Stamp).isRequired
  },

  // Override getBackboneState to tell the mixin
  // HOW to transform Backbone props into JSON state

  getBackboneState: function (props) {
    var stampModel = props.stampModel,
        primaryZineModel = stampModel.getPrimaryZine();

    return {
      stamp: stampModel.toJSON(),
      toggleIsLiked: stampModel.toggleIsLiked.bind(stampModel),
      primaryZine: primaryZineModel && primaryZineModel.toJSON()
    };
  },

  // Optionally override watchBackboneProps to tell the mixin
  // WHEN to transform Backbone props into JSON state

  watchBackboneProps: function (props, listenTo) {
    listenTo(props.stampModel, 'change:unauth_like_count change:is_liked');
    listenTo(props.stampModel.get('zines'), 'all');
  },

  render: function () {
    // You can use Vanilla JSON values of this.state.stamp,
    // this.state.toggleIsLiked and this.state.primaryZine
    // or whatever you return from getBackboneState
    // without worrying they may point to old values
  }
}
_

注:mixinにはUnderscore 1.6.0+が必要です。

17
Dan Abramov

私はBackbone.React.Componentの開発者です。 setPropsを使用する理由は、これがコンポーネント所有者(最大の親)によってのみ呼び出されることを意図しているためです。私の見方では、プロップは状態よりもリアクティブ更新(および子コンポーネントに渡す)に使用する方が良いですが、状態が優れている理由をいくつか指摘していただければ、その変化に向けて開発を開始させていただきます。

例えば、時々transferPropsToが非常に便利な、他の人に委任するコンポーネントがあります。状態を使用すると、それを達成するのが難しくなります。

8
Magalhas