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

IT技术 javascript backbone.js reactjs
2021-05-07 23:44:48

Facebook React 鼓励您将可变 ( state) 和不可变 ( props) 状态分开

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

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

一种方法来触发DOM更新手动调用forceUpdate,但它是灰心

通常,您应该尽量避免使用forceUpdate()并且只从this.propsthis.statein 中读取render()这使您的应用程序更加简单和高效。

然而,我看到的所有 React+Backbone 示例都忽略了这个建议,并将模型和集合存储在props并调用forceUpdate

甚至 React 自己的例子也使用forceUpdate

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

3个回答

皮特的回答很棒。

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

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

在有更好的答案之前,让我引用 核心 React 开发人员Pete Hunt 的话

Backbone 模型的最大优势在于它为您管理数据流。当您调用set()时,它会通知您的应用数据已更改。使用 React,你会发现这不是必要的,因为你需要做的就是通过回调通知拥有状态的组件,React 确保所有子组件都是最新的。所以这部分主干在 IMO 中不太有用(而且人们倾向于以这种方式在 React 中使用主干)。

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

您可以通过调用toJSON()您的主干模型并查看您喜欢它与传递模型来尝试这一点

(强调我的)

有趣的是,Backbone.React.Component是我发现的唯一一个使用 的例子toJSON,但由于某种原因也使用了setProps代替setState(这也是不鼓励的)。

更新

我根据 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+。

我是 Backbone.React.Component 背后的开发者。我们使用 setProps 的原因是因为它仅打算由组件所有者(最大父级)调用。在我看来,props 比 state 更适合用于响应式更新(并传递给子组件),但是如果您能指出一些为什么 state 更好的原因,我将很乐意开始朝着这种变化发展。

例如,有时我有委托给其他人的组件,其中 transferPropsTo 非常方便。使用 state 会使实现这一目标变得更加困难。