2014-09-23 3 views
1

Я уже учился в React.js, написав приложение небольшого калькулятора. Я думал, что все идет хорошо, пока я не узнал, что setState асинхронен, и поэтому мои мутации не сразу применяются.React.js Понимание setState

Таким образом, мой вопрос заключается в том, как наилучшим образом поддерживать текущую сумму на основе значений, добавляемых к вводу. Возьмем следующий пример:

var Calculator = React.createClass({ 
    total : 0, 

    getInitialState : function(){ 
    return { 
     value : '0' 
    }; 
    }, 

    onValueClicked : function (value) { 
    var actual, total, current = this.state.value; 

    if(value === '+') { 
     actual = this.total = parseInt(this.total, 10) + parseInt(current, 10); 
    } else { 
     if(parseInt(current, 10) === 0) { 
     actual = value; 
     } else { 
     actual = current.toString() + value; 
     } 
    } 

    this.setState({ value : actual }); 
    }, 

    render : function() { 
    return (
     <div className="calc-main"> 
     <CalcDisplay value={this.state.value} /> 
     <CalcButtonGroup range="0-10" onClick={this.onValueClicked} /> 
     <CalcOpButton type="+" onClick={this.onValueClicked} /> 
     </div> 
    ) 
    } 
}); 

var CalcDisplay = React.createClass({ 
    render : function() { 
    return (
     <input type="text" name="display" value={this.props.value} /> 
    ); 
    } 
}); 

var CalcButtonGroup = React.createClass({ 
    render : function() { 
    var i, buttons = [], range = this.props.range.split('-'); 

    for(i = range[0]; i < range[1]; i++) { 
     var handler = this.props.onClick.bind(null, i); 

     buttons.push(<CalcNumberButton key={i} onClick={ handler } />); 
    } 

    return (
     <div className="calc-btn-group">{ buttons }</div> 
    ); 
    } 
}); 

var CalcNumberButton = React.createClass({ 
    render : function() { 
    return (
     <button onClick={this.props.onClick}>{this.props.key}</button> 
    ); 
    } 
}); 

var CalcOpButton = React.createClass({ 
    render : function() { 
    var handler, op = this.props.type; 

    handler = this.props.onClick.bind(null, op); 

    return (
     <button onClick={handler}>{op}</button> 
    ); 
    } 
}); 

React.renderComponent(<Calculator />, document.getElementById('container')); 

В приведенном выше примере я полностью отказался от хранения суммы в пределах штата и оставил ее снаружи. Я прочитал, что вы можете запустить обратный вызов, когда setState закончил, но в случае калькулятора мне нужно, чтобы он был быстрым и быстро обновлялся. Если состояние не обновляется при каждом нажатии кнопки, и я быстро нажимаю кнопки - все будет выпадать из синхронизации. Является ли обратный вызов всем, что мне не хватает, или я думаю об этом совершенно неправильно?

Любая помощь оценена!

+0

Чтобы быть честным, если вам вообще не нужно использовать 'this.total' в функции' render', то вполне нормально держать его вне состояния. Материал должен находиться в состоянии, только если какое-либо изменение в нем должно «всегда» вызывать повторную визуализацию. Если вам действительно не нужно всегда перерисовывать при изменении this.total, то вы можете оставить вещи такими, какие они есть. –

+0

Если я не ошибаюсь, в текущей реализации, если вы вызываете 'setState' в ответ на событие, состояние будет зависеть до отправки любых дальнейших событий. –

+0

Я думаю, что часть проблемы заключается в том, что я был console.log'ing значения состояния. Однако я обнаружил, что если бы я попытался сохранить более одного элемента в состоянии и изменил одно поле в одном месте, а другое в другом месте - эти два значения выпадут из синхронизации. т. е. если я сохранил операнд в состоянии и проверил его существование в следующем взаимодействии - он не всегда будет установлен. – backdesk

ответ

2

Это асинхронный, но намного быстрее, чем самый быстрый человеческий клик.


Кроме того, вы должны объявить переменные экземпляра в компоненте Component, например.

componentDidMount: function(){ 
    this.total = 0; 
} 

... но в этом случае вы, вероятно, захотите сохранить его в состоянии.


.split возвращает массив строк, вы хотите использовать номера:

range = this.props.range.split('-').map(Number) 

Или избежать строки вообще (предпочитаемый) с одним из них:

<CalcButtonGroup range={[0, 10]} onClick={this.onValueClicked} /> 
<CalcButtonGroup range={{from: 0, till: 10}} onClick={this.onValueClicked} /> 
+0

Я не согласен с *, но в этом случае вы, вероятно, захотите сохранить его в состоянии *.«Состояние» только для состояния пользовательского интерфейса ** только **, используйте другую (подписную) переменную для логического состояния. –

+0

Вам нужны данные, которые используются для создания вывода рендера в состоянии + реквизит. Таким образом, вы можете сохранить общее количество или последовательность действий или, тем не менее, хотите их представлять. Но если это влияет на рендеринг, и это не в реквизитах или состоянии, вы быстро сталкиваетесь с проблемами, и все выпадает из синхронизации. – FakeRainBrigand

+0

Смотрите мое сообщение. Кроме того, другая идея разрешить *, если она влияет на рендеринг, а не в реквизитах или состоянии *, заключается в том, чтобы подписывать 'this.state' на эту переменную. И только эта переменная может обновить 'this.state'. Поток вот так: 'data' -> расчеты ->' data' -> 'this.state'. –

0

You определите переменную total для вашего состояния бизнес-логики. Почему бы не хранить больше информации?

var Calculator = React.createClass({ 
    previous: 0, // <-- previous result 
    current: 0, // <-- current display 
    op: '', // <-- pending operator 

    getInitialState : function(){ 
    return { 
     value : '0' 
    }; 
    }, 

    onValueClicked : function (value) { 
    var actual; 

    if(value === '+') { 
     this.previous = this.current; 
     this.op = '+'; 
     actual = 0; // start a new number 
    } else if (value === '=') { 
     if (this.op === '+') { 
     actual = this.previous + this.current; 
     } else { 
     actual = this.current; // no-op 
     } 
    } else { 
     actual = current * 10 + value; 
    } 

    this.current = actual; // <-- business logic state update is synchronous 
    this.setState({ value : String(actual) }); // <-- this.state is only for UI state, asynchronous just fine 
    }, 

    render : function() { 
    return (
     <div className="calc-main"> 
     <CalcDisplay value={this.state.value} /> 
     <CalcButtonGroup range="0-10" onClick={this.onValueClicked} /> 
     <CalcOpButton type="+" onClick={this.onValueClicked} /> 
     <CalcOpButton type="=" onClick={this.onValueClicked} /> 
     </div> 
    ) 
    } 
}); 

Основная идея разрешить this.state это использовать другие переменные для хранения бизнес-логика состояния, и запас this.state только для состояния пользовательского интерфейса.

PS. Реальный калькулятор имеет более сложную бизнес-логику, чем это. Вы должны четко определить каждое состояние и состояние машины в спецификации.

+0

Если вы хотите отделить бизнес-логику от состояния представления, переместите бизнес-логику в компонент контейнера, а не на свойства экземпляра на компоненте. –

Смежные вопросы