在与Backbone一起使用React时,我可以避免使用forceUpdate()吗?

时间:2014-02-11 18:19:09

标签: javascript backbone.js reactjs

Facebook React encourages您要将可变(state)和不可变(props)状态分开:

  

尽量保持尽可能多的组件无状态。通过这样做,您可以将状态隔离到最合理的位置并最大限度地减少冗余,从而更容易推断您的应用程序。

当状态发生变化时,你应该调用setState来触发虚拟DOM差异,这只会在需要时才会导致真正的DOM更新。

是一种通过调用forceUpdate手动触发DOM更新的方法,但它是discouraged

  

通常您应该尽量避免使用forceUpdate() ,并且只能在this.props中的this.staterender()中阅读。这使您的应用程序更加简单和高效。

但是,我看到的所有React + Backbone示例忽略此建议并在props中存储模型和集合并调用forceUpdate

即使React自己的例子也使用forceUpdate

是否有更好的方法,它会带来什么好处?

3 个答案:

答案 0 :(得分:18)

皮特的回答很棒。

骨干模型本质上是变异的,虽然(虽然本身不​​是问题)意味着在重新渲染时,你不会将旧版本的模型进行比较。这使得通过在组件的关键位置定义shouldComponentUpdate方法来进行智能优化变得更加困难。 (由于其他原因,例如implementing undo,您也失去了轻松存储旧版本模型的能力。)

调用forceUpdate只会跳过shouldComponentUpdate并强制组件重新呈现。请注意,调用render通常很便宜,如果render的输出发生变化,React仍然只会触及DOM,因此这里的性能问题并不常见。但是,如果你可以选择使用不可变数据(包括传递来自toJSON()的原始模型属性对象,如Pete建议的那样),我强烈推荐它。

答案 1 :(得分:17)

在有更好的答案之前,让我quote Pete Hunt,一个核心的React开发者:

  

Backbone模型的巨大成功是它为您管理数据流。当您致电set()时,它会通知您的应用数据已更改。 使用React,你会发现这不是必需的,因为您需要做的就是通过回调通知拥有该状态的组件,React确保所有孩子都是最新的。所以这个骨干的一部分是不太有用的IMO(并且人们倾向于以这种方式使用骨干而无论如何)。

     

您不必传递纯JSON(虽然这是我倾向于做的,它适用于简单的数据模型),但如果您保持对象不可变,您将看到很多优点。

     

你可以通过在骨干模型上调用toJSON()并看看你喜欢它与传递模型来尝试这一点。

(强调我的)

有趣的是,Backbone.React.Component是我发现使用toJSON的唯一示例,但出于某种原因,还使用setProps代替setStatediscouraged too )。

更新

我根据Pete Hunt的方法制作了一个简单的mixin(没有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告诉mixin何时使用新值调用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 +。

答案 2 :(得分:8)

我是Backbone.React.Component背后的开发人员。我们使用setProps的原因是因为它只是由组件所有者(最大的父级)调用。我看待它的方式,道具最好用于反应性更新(并传递给子组件)而不是状态,但如果你能指出一些原因为什么状态更好,我会很乐意开始朝着这种变化发展。 / p>

例如,有时我会将组件委托给其他人,其中transferPropsTo非常方便。使用状态使得实现更难。