处理 react.js 中的主干模型/集合更改

IT技术 javascript backbone.js reactjs
2021-05-15 01:06:24

在过去的几周里,我一直在使用 facebooks 框架 React.js 和 Backbone,但我仍然不完全确定当 Backbone 集合发生变化时重新渲染 React 组件的最合适方法是什么已作为props传入。

目前我所做的是在componenentWillMount集合上设置change/add/remove侦听器并在触发时设置状态:

componentWillMount: function(){
    var myCollection = this.props.myCollection;
    var updateState = function(){
        this.setState({myCollection: myCollection.models});
    }

    myCollections.on("add remove", updateState, this);
    updateState();
}

render: function(){
    var listItems = this.state.myCollection.map(function(item){
        return <li>{item.get("someAttr")}</li>;
    });
    return <ul>{listItems}</ul>;
}

我见过将模型克隆到状态的示例:

var updateState = function () {
    this.setState({ myCollection: _.clone(this.myCollection.models) });
};

我还看到了在 props 中直接使用模型/集合而不是使用状态的变体,然后在集合/模型更改时调用 forceUpdate,导致组件重新渲染

componentWillMount: function(){
    var myCollection = this.props.myCollection;
    myCollections.on("add remove", this.forceUpdate, this);
}

render: function(){
    var listItems = this.props.myCollection.map(function(item){
        return <li>{item.get("someAttr")}</li>;
    });
    return <ul>{listItems}</ul>;
}

不同的方法有什么优点和缺点?有没有办法做到这一点,那就是React 的方式

4个回答

您可以使用基于此 BackboneMixin 的 mixin 来帮助自动绑定和取消绑定侦听器,而不是手动绑定事件侦听器:

https://github.com/facebook/react/blob/1be9a9e/examples/todomvc-backbone/js/app.js#L148-L171

然后你只需写

var List = React.createClass({
    mixins: [BackboneMixin],

    getBackboneModels: function() {
        return [this.props.myCollection];
    },

    render: function(){
        var listItems = this.props.myCollection.map(function(item){
            return <li>{item.get("someAttr")}</li>;
        });
        return <ul>{listItems}</ul>;
    }
});

当集合中的任何内容发生变化时,组件将被重新渲染。你只需要将 BackboneMixin 放在顶级组件上——任何后代都会同时自动重新渲染。

IMO,React 仍然很新,关于如何使用数据和像 Backbone 这样的react模型的既定规则很少。这也是一个优势,如果你有一个现有的应用程序——react 可以集成到它的一些较小的部分上,而无需重新定义整个数据流。

我相信由于 React 可以随时调用“智能”渲染——这只是重新渲染已更改的部分——你真的不需要将数据作为状态传递。只需传递数据,在顶部组件上添加侦听器并forceUpdate在模型更改时调用,它就会很好地向下传播。

将主干模型作为props而不是状态传递似乎更“正确”。

我通过艰难的方式学到的一件重要的事情是在渲染主干模型列表时使用model.cidas 键(而不是Math.random()):

var listItems = this.props.myCollection.map(function(item){
    return <li key={item.cid}>{item.get("someAttr")}</li>;
});

因为否则 React 将无法识别要重新渲染的模型,因为它们在每次渲染时都会有新的键。

我一直在玩弄这里提到的 BackboneMixin 和其他一些react资源(目前有限的信息)。我发现当我在监听从服务器更新的集合时,会在集合上触发 n 个“添加”事件并被 BackboneMixin 监听,因此调用 force update n 次,它调用 render 以及从 render n 次调用的任何内容。

相反,我使用下划线/lo-dash 的油门方法来限制调用 forceUpdate 的次数。至少这限制了渲染方法被调用这么多。我知道 react 实际上并没有在那里进行任何 DOM 操作,它只是一个虚拟 DOM,但仍然没有理由为 100 次立即添加到集合中而应调用 100 次。

所以我的解决方案看起来像https://gist.github.com/ssorallen/7883081但使用像这样的 componentDidMount 方法:

componentDidMount: function() {
  //forceUpdate will be called at most once every second
  this._boundForceUpdate = _.throttle(this.forceUpdate.bind(this, null), 1000);
  this.getBackboneObject().on("all", this._boundForceUpdate, this);
}

Eldar Djafarov 提供了另一个 BackboneMixin,它在模型更改时重新渲染您的组件,并且还提供了一种非常方便的方式来获得双向数据绑定:

  var BackboneMixin = {
    /* Forces an update when the underlying Backbone model instance has
     * changed. Users will have to implement getBackboneModels().
     * Also requires that React is loaded with addons.
     */
    __syncedModels: [],
    componentDidMount: function() {
      // Whenever there may be a change in the Backbone data, trigger a reconcile.
      this.getBackboneModels().forEach(this.injectModel, this);
    },
    componentWillUnmount: function() {
      // Ensure that we clean up any dangling references when the component is
      // destroyed.
      this.__syncedModels.forEach(function(model) {
        model.off(null, model.__updater, this);
      }, this);
    },
    injectModel: function(model){
      if(!~this.__syncedModels.indexOf(model)){
        var updater = this.forceUpdate.bind(this, null);
        model.__updater = updater;
        model.on('add change remove', updater, this);
      }
    },
    bindTo: function(model, key){
      /* Allows for two-way databinding for Backbone models.
       * Use by passing it as a 'valueLink' property, e.g.:
       *   valueLink={this.bindTo(model, attribute)} */
      return {
        value: model.get(key),
        requestChange: function(value){
            model.set(key, value);
        }.bind(this)
      };
    }
  }

这是他的 jsFiddle 演示用法:http : //jsfiddle.net/djkojb/qZf48/13/