React.js имеет состояние, основанное на другом состоянии
У меня возникают проблемы с React.js, и состояние, которое не устанавливается сразу при вызове setState(). Я не уверен, есть ли лучшие способы приблизиться к этому, или если это действительно является недостатком React. У меня есть две переменные состояния, одна из которых основана на другой. (Fiddle of original problem: http://jsfiddle.net/kb3gN/4415/ вы можете видеть в журналах, которые он не установил сразу, когда вы нажимаете кнопку)
setAlarmTime: function(time) {
this.setState({ alarmTime: time });
this.checkAlarm();
},
checkAlarm: function() {
this.setState({
alarmSet: this.state.alarmTime > 0 && this.state.elapsedTime < this.state.alarmTime
});
}, ...
При вызове setAlarmTime
, так как this.state.alarmTime
не обновляется немедленно, следующий вызов checkAlarm
устанавливает alarmSet
на основе предыдущего значения this.state.alarmTime
и поэтому неверен.
Я решил это, переместив вызов checkAlarm
в обратный вызов setState
в setAlarmTime
, но нужно следить за тем, какое состояние на самом деле является "правильным" и пытаться вставить все в обратные вызовы, кажется смешным:
setAlarmTime: function(time) {
this.setState({ alarmTime: time }, this.checkAlarm);
}
Есть ли лучший способ сделать это? В моем коде есть несколько других мест, которые я ссылаюсь на состояние, которое я только что установил, и теперь я не уверен, когда я действительно могу доверять состоянию!
Спасибо
Ответы
Ответ 1
Да, setState является асинхронным, поэтому this.state
не будет обновляться немедленно. Ниже приведены теги для пакетной обработки, что может объяснить некоторые детали.
В приведенном выше примере alarmSet
- это данные, вычисленные из состояний alarmTime
и elapsedTime
. Вообще говоря, вычисленные данные не должны храниться в состоянии объекта, а должны вычисляться по мере необходимости как часть метода рендеринга. Существует раздел What Shouldnt Go in State? в нижней части документа Interactivity и Dynamic UIs, в котором приводятся примеры таких вещей, которые не должны перейти в состояние, а раздел What Components Should Have State? объясняет некоторые причины, почему это может быть хорошей идеей.
Ответ 2
Как сказал Дуглас, обычно не рекомендуется сохранять вычисленное состояние в this.state
, а вместо этого перепрограммировать его каждый раз в компоненте render
, так как состояние будет обновлено этой точкой.
Однако это не сработает для меня, так как мой компонент фактически имеет свой собственный цикл обновления, который должен проверять и, возможно, обновлять его состояние при каждом тике. Поскольку мы не можем рассчитывать на this.state
для обновления на каждом тике, я создал обходное решение, которое обертывает React.createClass
и добавляет его собственное внутреннее отслеживание состояния. (Требуется jQuery для $.extend) (Fiddle: http://jsfiddle.net/kb3gN/4448/)
var Utils = new function() {
this.createClass = function(object) {
return React.createClass(
$.extend(
object,
{
_state: object.getInitialState ? object.getInitialState() : {},
_setState: function(newState) {
$.extend(this._state, newState);
this.setState(newState);
}
}
)
);
}
}
Для любых компонентов, где вам требуется обновленное состояние вне функции рендеринга, просто замените вызов на React.createClass
на Utils.createClass
.
Вам также придется менять все вызовы this.setState
с помощью вызовов this._setState
и this.state
с помощью this._state
.
Последним следствием этого является то, что вы потеряете автоматически сгенерированное свойство displayName
в своем компоненте. Это связано с заменой jsx-трансформатора на
var anotherComponent = React.createClass({
с
var anotherComponent = React.createClass({displayName: 'anotherComponent'
.
Чтобы обойти это, вам просто нужно вручную добавить свойство displayName к вашим объектам.
Надеюсь, что это поможет
Ответ 3
Не уверен, что это было так, когда задавался вопрос, хотя теперь второй параметр this.setState(stateChangeObject, callback)
принимает необязательную функцию обратного вызова, поэтому может сделать это:
setAlarmTime: function(time) {
this.setState({ alarmTime: time }, this.checkAlarm);
},
checkAlarm: function() {
this.setState({
alarmSet: this.state.alarmTime > 0 && this.state.elapsedTime < this.state.alarmTime
});
}, ...