我正在开发一个应用程序,在该应用程序中,我希望计时器从60秒倒计时到0,然后更改一些内容,然后计时器在60再次重启。
我已经在React和Flux中实现了它,但是由于我是新手,所以我仍然遇到一些问题。
我现在想为计时器添加一个开始/停止按钮。我不确定在哪里放置/处理计时器状态。
我有一个组件Timer.jsx,看起来像这样:
var React = require('react');
var AppStore = require('../stores/app-store.js');
var AppActions = require('../actions/app-actions.js');
function getTimeLeft() {
return {
timeLeft: AppStore.getTimeLeft()
}
}
var Timer = React.createClass({
_tick: function() {
this.setState({ timeLeft: this.state.timeLeft - 1 });
if (this.state.timeLeft < 0) {
AppActions.changePattern();
clearInterval(this.interval);
}
},
_onChange: function() {
this.setState(getTimeLeft());
this.interval = setInterval(this._tick, 1000);
},
getInitialState: function() {
return getTimeLeft();
},
componentWillMount: function() {
AppStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
componentDidMount: function() {
this.interval = setInterval(this._tick, 1000);
},
render: function() {
return (
<small>
({ this.state.timeLeft })
</small>
)
}
});
module.exports = Timer;
var _timeLeft = 60;dispatcherIndex: AppDispatcher.register(function(payload) {
var action = payload.action;
switch(action.actionType) {
case AppConstants.START_TIMER:
// do something
break;
case AppConstants.STOP_TIMER:
// do something
break;
case AppConstants.CHANGE_PATTERN:
_setPattern();
break;
}
AppStore.emitChange();
return true;
})
最佳答案
我最终下载了您的代码并实现了所需的启动/停止/重置功能。我认为这可能是解释问题的最佳方法-显示可以运行和测试的代码以及一些注释。
实际上,我最终得到了两种实现。我将它们称为实现A和实现B。
我认为展示这两种实现会很有趣。希望它不会引起太多困惑。
记录下来,实现A是更好的版本。
以下是这两种实现的简要说明:
实现A
此版本在App组件级别跟踪状态。通过将props传递到Timer组件来管理计时器。计时器组件确实会跟踪自己的剩余时间状态。
实现B
此版本使用TimerStore和TimerAction模块来管理组件的状态和事件,从而在Timer组件级别跟踪计时器状态。
实现B的最大(可能是致命的)缺点是您只能拥有一个Timer组件。这是由于TimerStore和TimerAction模块本质上是Singletons。
|
|
实现A
|
|
此版本在App组件级别跟踪状态。这里的大多数注释都在该版本的代码中。
通过将props传递给计时器来管理计时器。
此实现的代码更改 list :
module.exports = {
START_TIMER: 'START_TIMER',
STOP_TIMER: 'STOP_TIMER',
RESET_TIMER: 'RESET_TIMER',
CHANGE_PATTERN: 'CHANGE_PATTERN'
};
var AppConstants = require('../constants/app-constants.js');
var AppDispatcher = require('../dispatchers/app-dispatcher.js');
var AppActions = {
changePattern: function() {
AppDispatcher.handleViewAction({
actionType: AppConstants.CHANGE_PATTERN
})
},
resetTimer: function() {
AppDispatcher.handleViewAction({
actionType: AppConstants.RESET_TIMER
})
},
startTimer: function() {
AppDispatcher.handleViewAction({
actionType: AppConstants.START_TIMER
})
},
stopTimer: function() {
AppDispatcher.handleViewAction({
actionType: AppConstants.STOP_TIMER
})
}
};
module.exports = AppActions;
var AppDispatcher = require('../dispatchers/app-dispatcher.js');
var AppConstants = require('../constants/app-constants.js');
var EventEmitter = require('events').EventEmitter;
var merge = require('react/lib/Object.assign');
// I added a TimerStatus model (probably could go in its own file)
// to manage whether the timer is "start/stop/reset".
//
// The reason for this is that reset state was tricky to handle since the Timer
// component no longer has access to the "AppStore". I'll explain the reasoning for
// that later.
//
// To solve that problem, I added a `reset` method to ensure the state
// didn't continuously loop "reset". This is probably not very "Flux".
//
// Maybe a more "Flux" alternative is to use a separate TimerStore and
// TimerAction?
//
// You definitely don't want to put them in AppStore and AppAction
// to make your timer component more reusable.
//
var TimerStatus = function(status) {
this.status = status;
};
TimerStatus.prototype.isStart = function() {
return this.status === 'start';
};
TimerStatus.prototype.isStop = function() {
return this.status === 'stop';
};
TimerStatus.prototype.isReset = function() {
return this.status === 'reset';
};
TimerStatus.prototype.reset = function() {
if (this.isReset()) {
this.status = 'start';
}
};
var CHANGE_EVENT = "change";
var shapes = ['C', 'A', 'G', 'E', 'D'];
var rootNotes = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'];
var boxShapes = require('../data/boxShapes.json');
// Added a variable to keep track of timer state. Note that this state is
// managed by the *App Component*.
var _timerStatus = new TimerStatus('start');
var _pattern = _setPattern();
function _setPattern() {
var rootNote = _getRootNote();
var shape = _getShape();
var boxShape = _getBoxForShape(shape);
_pattern = {
rootNote: rootNote,
shape: shape,
boxShape: boxShape
};
return _pattern;
}
function _getRootNote() {
return rootNotes[Math.floor(Math.random() * rootNotes.length)];
}
function _getShape() {
return shapes[Math.floor(Math.random() * shapes.length)];
}
function _getBoxForShape(shape) {
return boxShapes[shape];
}
// Simple function that creates a new instance of TimerStatus set to "reset"
function _resetTimer() {
_timerStatus = new TimerStatus('reset');
}
// Simple function that creates a new instance of TimerStatus set to "stop"
function _stopTimer() {
_timerStatus = new TimerStatus('stop');
}
// Simple function that creates a new instance of TimerStatus set to "start"
function _startTimer() {
_timerStatus = new TimerStatus('start');
}
var AppStore = merge(EventEmitter.prototype, {
emitChange: function() {
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
// Added this function to get timer status from App Store
getTimerStatus: function() {
return _timerStatus;
},
getPattern: function() {
return _pattern;
},
dispatcherIndex: AppDispatcher.register(function(payload) {
var action = payload.action;
switch(action.actionType) {
case AppConstants.RESET_TIMER:
// Handle reset action
_resetTimer();
break;
case AppConstants.START_TIMER:
// Handle start action
_startTimer();
break;
case AppConstants.STOP_TIMER:
// Handle stop action
_stopTimer();
break;
case AppConstants.CHANGE_PATTERN:
_setPattern();
break;
}
AppStore.emitChange();
return true;
})
});
module.exports = AppStore;
var React = require('react');
var Headline = require('./components/Headline.jsx');
var Scale = require('./components/Scale.jsx');
var RootNote = require('./components/RootNote.jsx');
var Shape = require('./components/Shape.jsx');
var Timer = require('./components/Timer.jsx');
// Removed AppActions and AppStore from Timer component and moved
// to App component. This is done to to make the Timer component more
// reusable.
var AppActions = require('./actions/app-actions.js');
var AppStore = require('./stores/app-store.js');
// Use the AppStore to get the timerStatus state
function getAppState() {
return {
timerStatus: AppStore.getTimerStatus()
}
}
var App = React.createClass({
getInitialState: function() {
return getAppState();
},
// Listen for change events in AppStore
componentDidMount: function() {
AppStore.addChangeListener(this.handleChange);
},
// Stop listening for change events in AppStore
componentWillUnmount: function() {
AppStore.removeChangeListener(this.handleChange);
},
// Timer component has status, defaultTimeout attributes.
// Timer component has an onTimeout event (used for changing pattern)
// Add three basic buttons for Start/Stop/Reset
render: function() {
return (
<div>
<header>
<Headline />
<Scale />
</header>
<section>
<RootNote />
<Shape />
<Timer status={this.state.timerStatus} defaultTimeout="15" onTimeout={this.handleTimeout} />
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<button onClick={this.handleClickReset}>Reset</button>
</section>
</div>
);
},
// Handle change event from AppStore
handleChange: function() {
this.setState(getAppState());
},
// Handle timeout event from Timer component
// This is the signal to change the pattern.
handleTimeout: function() {
AppActions.changePattern();
},
// Dispatch respective start/stop/reset actions
handleClickStart: function() {
AppActions.startTimer();
},
handleClickStop: function() {
AppActions.stopTimer();
},
handleClickReset: function() {
AppActions.resetTimer();
}
});
module.exports = App;
Timer和AppStore依赖项以使AppActions组件更可重用以来,Timer也进行了许多更改。详细注释在代码中。var React = require('react');
// Add a default timeout if defaultTimeout attribute is not specified.
var DEFAULT_TIMEOUT = 60;
var Timer = React.createClass({
// Normally, shouldn't use props to set state, however it is OK when we
// are not trying to synchronize state/props. Here we just want to provide an option to specify
// a default timeout.
//
// See http://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html)
getInitialState: function() {
this.defaultTimeout = this.props.defaultTimeout || DEFAULT_TIMEOUT;
return {
timeLeft: this.defaultTimeout
};
},
// Changed this to `clearTimeout` instead of `clearInterval` since I used `setTimeout`
// in my implementation
componentWillUnmount: function() {
clearTimeout(this.interval);
},
// If component updates (should occur when setState triggered on Timer component
// and when App component is updated/re-rendered)
//
// When the App component updates we handle two cases:
// - Timer start status when Timer is stopped
// - Timer reset status. In this case, we execute the reset method of the TimerStatus
// object to set the internal status to "start". This is to avoid an infinite loop
// on the reset case in componentDidUpdate. Kind of a hack...
componentDidUpdate: function() {
if (this.props.status.isStart() && this.interval === undefined) {
this._tick();
} else if (this.props.status.isReset()) {
this.props.status.reset();
this.setState({timeLeft: this.defaultTimeout});
}
},
// On mount start ticking
componentDidMount: function() {
this._tick();
},
// Tick event uses setTimeout. I find it easier to manage than setInterval.
// We just keep calling setTimeout over and over unless the timer status is
// "stop".
//
// Note that the Timer states is handled here without a store. You could probably
// say this against the rules of "Flux". But for this component, it just seems unnecessary
// to create separate TimerStore and TimerAction modules.
_tick: function() {
var self = this;
this.interval = setTimeout(function() {
if (self.props.status.isStop()) {
self.interval = undefined;
return;
}
self.setState({timeLeft: self.state.timeLeft - 1});
if (self.state.timeLeft <= 0) {
self.setState({timeLeft: self.defaultTimeout});
self.handleTimeout();
}
self._tick();
}, 1000);
},
// If timeout event handler passed to Timer component,
// then trigger callback.
handleTimeout: function() {
if (this.props.onTimeout) {
this.props.onTimeout();
}
}
render: function() {
return (
<small className="timer">
({ this.state.timeLeft })
</small>
)
},
});
module.exports = Timer;
module.exports = {
START_TIMER: 'START_TIMER',
STOP_TIMER: 'STOP_TIMER',
RESET_TIMER: 'RESET_TIMER',
TIMEOUT: 'TIMEOUT',
TICK: 'TICK'
};
var AppConstants = require('../constants/app-constants.js');
var AppDispatcher = require('../dispatchers/app-dispatcher.js');
module.exports = {
// This event signals when the timer expires.
// We can use this to change the pattern.
timeout: function() {
AppDispatcher.handleViewAction({
actionType: AppConstants.TIMEOUT
})
},
// This event decrements the time left
tick: function() {
AppDispatcher.handleViewAction({
actionType: AppConstants.TICK
})
},
// This event sets the timer state to "start"
start: function() {
AppDispatcher.handleViewAction({
actionType: AppConstants.START_TIMER
})
},
// This event sets the timer state to "stop"
stop: function() {
AppDispatcher.handleViewAction({
actionType: AppConstants.STOP_TIMER
})
},
// This event resets the time left and sets the state to "start"
reset: function() {
AppDispatcher.handleViewAction({
actionType: AppConstants.RESET_TIMER
})
},
};
AppStore中分离出了计时器内容。这是为了使Timer组件更具可重用性。var AppDispatcher = require('../dispatchers/app-dispatcher.js');
var AppConstants = require('../constants/app-constants.js');
var EventEmitter = require('events').EventEmitter;
var merge = require('react/lib/Object.assign');
var CHANGE_EVENT = "change";
var TIMEOUT_SECONDS = 15;
var _timerStatus = 'start';
var _timeLeft = TIMEOUT_SECONDS;
function _resetTimer() {
_timerStatus = 'start';
_timeLeft = TIMEOUT_SECONDS;
}
function _stopTimer() {
_timerStatus = 'stop';
}
function _startTimer() {
_timerStatus = 'start';
}
function _decrementTimer() {
_timeLeft -= 1;
}
var TimerStore = merge(EventEmitter.prototype, {
emitChange: function() {
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getTimeLeft: function() {
return _timeLeft;
},
getStatus: function() {
return _timerStatus;
},
dispatcherIndex: AppDispatcher.register(function(payload) {
var action = payload.action;
switch(action.actionType) {
case AppConstants.START_TIMER:
_startTimer();
break;
case AppConstants.STOP_TIMER:
_stopTimer();
break;
case AppConstants.RESET_TIMER:
_resetTimer();
break;
case AppConstants.TIMEOUT:
_resetTimer();
break;
case AppConstants.TICK:
_decrementTimer();
break;
}
TimerStore.emitChange();
return true;
})
});
module.exports = TimerStore;
pattern-store.js,尽管您需要进行一些更改以使其可重用。具体来说,我直接在监听Timer的TIMEOUT Action /事件以触发模式更改。如果要重用模式更改,则可能不希望依赖。例如,如果您想通过单击按钮或其他方式来更改图案。AppStore中删除了所有与Timer相关的功能。var AppDispatcher = require('../dispatchers/app-dispatcher.js');
var AppConstants = require('../constants/app-constants.js');
var EventEmitter = require('events').EventEmitter;
var merge = require('react/lib/Object.assign');
var CHANGE_EVENT = "change";
var shapes = ['C', 'A', 'G', 'E', 'D'];
var rootNotes = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'];
var boxShapes = require('../data/boxShapes.json');
var _pattern = _setPattern();
function _setPattern() {
var rootNote = _getRootNote();
var shape = _getShape();
var boxShape = _getBoxForShape(shape);
_pattern = {
rootNote: rootNote,
shape: shape,
boxShape: boxShape
};
return _pattern;
}
function _getRootNote() {
return rootNotes[Math.floor(Math.random() * rootNotes.length)];
}
function _getShape() {
return shapes[Math.floor(Math.random() * shapes.length)];
}
function _getBoxForShape(shape) {
return boxShapes[shape];
}
var AppStore = merge(EventEmitter.prototype, {
emitChange: function() {
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getPattern: function() {
return _pattern;
},
dispatcherIndex: AppDispatcher.register(function(payload) {
var action = payload.action;
switch(action.actionType) {
case AppConstants.TIMEOUT:
_setPattern();
break;
}
AppStore.emitChange();
return true;
})
});
module.exports = AppStore;
TimerAction.stop()var React = require('react');
var Headline = require('./components/Headline.jsx');
var Scale = require('./components/Scale.jsx');
var RootNote = require('./components/RootNote.jsx');
var Shape = require('./components/Shape.jsx');
var Timer = require('./components/Timer.jsx');
var TimerActions = require('./actions/timer-actions.js');
var App = React.createClass({
render: function() {
return (
<div>
<header>
<Headline />
<Scale />
</header>
<section>
<RootNote />
<Shape />
<Timer />
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<button onClick={this.handleClickReset}>Reset</button>
</section>
</div>
);
},
handleClickStart: function() {
TimerActions.start();
},
handleClickStop: function() {
TimerActions.stop();
},
handleClickReset: function() {
TimerActions.reset();
}
});
module.exports = App;
setTimeout而不是setInterval。我发现setTimeout更易于管理。_tick方法中。基本上,只要状态为“开始”,我们就继续调用setTimeout。timeout事件的信号。 TimerStore和AppStore正在侦听此事件。componentDidUpdate钩子(Hook)来处理。当组件的状态更改或父组件被重新渲染时,将调用此挂钩。componentDidUpdate方法中,只有在状态为“开始”且未定义超时标识符的情况下,我们才确保启动“计时”。我们不希望运行多个setTimeouts。var React = require('react');
var TimerActions = require('../actions/timer-actions.js');
var TimerStore = require('../stores/timer-store.js');
function getTimerState() {
return {
status: TimerStore.getStatus(),
timeLeft: TimerStore.getTimeLeft()
}
}
var Timer = React.createClass({
_tick: function() {
var self = this;
this.interval = setTimeout(function() {
if (self.state.status === 'stop') {
self.interval = undefined;
return;
}
if (self.state.timeLeft <= 0) {
TimerActions.timeout();
} else {
TimerActions.tick();
}
self._tick();
}, 1000);
},
getInitialState: function() {
return getTimerState();
},
componentDidMount: function() {
TimerStore.addChangeListener(this.handleChange);
this._tick();
},
componentWillUnmount: function() {
clearTimeout(this.interval);
TimerStore.removeChangeListener(this.handleChange);
},
handleChange: function() {
this.setState(getTimerState());
},
componentDidUpdate: function() {
if (this.state.status === 'start' && this.interval === undefined) {
this._tick();
}
},
render: function() {
return (
<small className="timer">
({ this.state.timeLeft })
</small>
)
}
});
module.exports = Timer;
关于javascript - 在React/Flux中处理计时器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27604431/
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的
我对图像处理完全陌生。我对JPEG内部是什么以及它是如何工作一无所知。我想知道,是否可以在某处找到执行以下简单操作的ruby代码:打开jpeg文件。遍历每个像素并将其颜色设置为fx绿色。将结果写入另一个文件。我对如何使用ruby-vips库实现这一点特别感兴趣https://github.com/ender672/ruby-vips我的目标-学习如何使用ruby-vips执行基本的图像处理操作(Gamma校正、亮度、色调……)任何指向比“helloworld”更复杂的工作示例的链接——比如ruby-vips的github页面上的链接,我们将不胜感激!如果有ruby-
我有一个super简单的脚本,它几乎包含了FayeWebSocketGitHub页面上用于处理关闭连接的内容:ws=Faye::WebSocket::Client.new(url,nil,:headers=>headers)ws.on:opendo|event|p[:open]#sendpingcommand#sendtestcommand#ws.send({command:'test'}.to_json)endws.on:messagedo|event|#hereistheentrypointfordatacomingfromtheserver.pJSON.parse(event.d
我正在寻找一个用ruby演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent
我正在尝试解析网页,但有时会收到404错误。这是我用来获取网页的代码:result=Net::HTTP::getURI.parse(URI.escape(url))如何测试result是否为404错误代码? 最佳答案 像这样重写你的代码:uri=URI.parse(url)result=Net::HTTP.start(uri.host,uri.port){|http|http.get(uri.path)}putsresult.codeputsresult.body这将打印状态码和正文。
我有一个将某些事件写入队列的Rails3应用。现在我想在服务器上创建一个服务,每x秒轮询一次队列,并按计划执行其他任务。除了创建ruby脚本并通过cron作业运行它之外,还有其他稳定的替代方案吗? 最佳答案 尽管启动基于Rails的持久任务是一种选择,但您可能希望查看更有序的系统,例如delayed_job或Starling管理您的工作量。我建议不要在cron中运行某些东西,因为启动整个Rails堆栈的开销可能很大。每隔几秒运行一次它是不切实际的,因为Rails上的启动时间通常为5-15秒,具体取决于您的硬件。不过,每天这样做几
我查看了Stripedocumentationonerrors,但我仍然无法正确处理/重定向这些错误。基本上无论发生什么,我都希望他们返回到edit操作(通过edit_profile_path)并向他们显示一条消息(无论成功与否)。我在edit操作上有一个表单,它可以POST到update操作。使用有效的信用卡可以正常工作(费用在Stripe仪表板中)。我正在使用Stripe.js。classExtrasController5000,#amountincents:currency=>"usd",:card=>token,:description=>current_user.email)
我有这个:AccountSummary我想单击该链接,但在使用link_to时出现错误。我试过:bot.click(page.link_with(:href=>/menu_home/))bot.click(page.link_with(:class=>'top_level_active'))bot.click(page.link_with(:href=>/AccountSummary/))我得到的错误是:NoMethodError:nil:NilClass的未定义方法“[]” 最佳答案 那是一个javascript链接。Mechan
当profile为nil时,总是让我感到悲伤...我该怎么办? 最佳答案 在View中使用变量之前,始终检查变量是否为nil。我确信这个问题有更优雅的解决方案,但这应该能让您入门。 关于ruby-on-rails-Rails处理.Erb与Nils,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/2709605/