Могу ли я избегать forceUpdate() при использовании React with Backbone?
Реагировать на Facebook поощряет отделить изменчивое (state
) и неизменяемое (props
) состояние:
Постарайтесь сохранить как можно больше ваших компонентов без гражданства. Делая это, вы изолируете состояние в наиболее логичном месте и минимизируете избыточность, что упрощает рассуждение о вашем приложении.
При изменении состояния вы должны вызвать setState
для запуска виртуального DOM diff, что приведет к реальному обновлению DOM только тогда, когда это необходимо.
Существует способ инициировать обновление DOM вручную, вызывая forceUpdate
, но обескуражен:
Обычно следует избегать использования forceUpdate()
и читать только this.props
и this.state
в render()
. Это делает ваше приложение намного проще и эффективнее.
Однако все примеры React + Backbone, которые я видел , игнорируют этот совет и сохраняют модели и коллекции в props
и вызывают forceUpdate
:
В собственном примере Even React используется forceUpdate
:
Есть ли лучший способ, и какие выгоды он даст?
Ответы
Ответ 1
Ответ Пита велик.
Магистральные модели по своей сути являются мутативными, что (хотя и не само по себе) означает, что при рендеринге вы не будете сравнивать старую версию модели. Это затрудняет интеллектуальную оптимизацию, определяя методы shouldComponentUpdate
в ключевых местах ваших компонентов. (Вы также теряете способность легко хранить старые версии своей модели по другим причинам, например отменить.)
Вызов forceUpdate
просто пропускает shouldComponentUpdate
и заставляет компонент переименовывать. Обратите внимание, что вызов render
обычно дешевый, и React по-прежнему будет касаться только DOM, если результат render
изменился, поэтому проблемы с производительностью здесь не являются общими. Однако, если у вас есть выбор использовать неизменяемые данные (в том числе прохождение вокруг объектов свойств модели модели из toJSON()
, как предлагает Пит), я бы очень рекомендовал его.
Ответ 2
Пока не будет лучшего ответа, позвольте мне quote Пит Хант, разработчик ядра React:
Большая победа с моделями Backbone позволила вам управлять потоком данных для вас. Когда вы позвонили set()
, оно сообщит вашему приложению, что данные изменены. С помощью React вы найдете это менее необходимым, потому что все, что вам нужно сделать, это сообщить компоненту, которому принадлежит состояние через обратный вызов, и React гарантирует, что все дети обновлены. Таким образом, это часть позвоночника менее полезна ИМО (и люди, как правило, используют так называемую магистраль с React).
Вам не нужно передавать чистый JSON (хотя то, что я обычно делаю, и он хорошо работает для простых моделей данных), но вы увидите много преимуществ, если сохраните свои объекты неизменными.
Вы можете попробовать это, просто набрав toJSON()
на своих базовых моделях и посмотрев, как вам это нравится, и передавайте модели вокруг.
(акцент мой)
Интересно, что Backbone.React.Component - единственный пример, который я нашел, который использует toJSON
, но по какой-то причине также использует setProps
вместо setState
(который обескуражен).
Update
Я сделал простой микс на основе подхода Пит Хант (нет setProps
, no 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 +.
Ответ 3
Я разработчик Backbone.React.Component. Причина, по которой мы используем setProps, состоит в том, что она предназначена только для того, чтобы ее вызывал владелец компонента (наибольший родитель). То, как я это вижу, реквизит лучше использовать для реактивных обновлений (и перейти к дочерним компонентам), чем состояние, но если вы можете указать мне некоторые причины, почему состояние лучше, я с удовольствием начну развиваться в сторону этих изменений.
Например, иногда у меня есть компоненты, которые делегируют другим, где transferPropsTo очень удобен. Использование состояния типа затрудняет достижение этого.