我一直在努力制作一个反应和减速秒表.我一直在努力弄清楚如何在redux中设计这样的东西.
首先想到的是有一个START_TIMER
设置初始offset
值的动作.在那之后,我用来反复setInterval
触发一个TICK
动作,通过使用偏移计算已经过了多少时间,将其添加到当前时间,然后更新offset
.
这种方法似乎有效,但我不确定如何清除间隔来阻止它.此外,似乎这种设计很差,可能有更好的方法.
这是一个完整的JSFiddle,它具有START_TIMER
正常运行的功能.如果您只是想看看我的减速机现在的样子,这里是:
const initialState = { isOn: false, time: 0 }; const timer = (state = initialState, action) => { switch (action.type) { case 'START_TIMER': return { ...state, isOn: true, offset: action.offset }; case 'STOP_TIMER': return { ...state, isOn: false }; case 'TICK': return { ...state, time: state.time + (action.time - state.offset), offset: action.time }; default: return state; } }
我真的很感激任何帮助.
我可能会建议以不同的方式进行此操作:仅存储计算商店中已用时间所需的状态,并让组件设置自己的间隔,但通常他们希望更新显示.
这可以将操作调度保持在最低限度 - 仅调度启动和停止(和重置)计时器的操作.请记住,每次调度操作时都会返回一个新的状态对象,然后每个connect
ed组件重新渲染(即使它们使用优化来避免在包装组件中重新渲染太多).此外,许多操作调度可能使调试应用程序状态更改变得困难,因为您必须TICK
与其他操作一起处理所有s.
这是一个例子:
// Action Creators function startTimer(baseTime = 0) { return { type: "START_TIMER", baseTime: baseTime, now: new Date().getTime() }; } function stopTimer() { return { type: "STOP_TIMER", now: new Date().getTime() }; } function resetTimer() { return { type: "RESET_TIMER", now: new Date().getTime() } } // Reducer / Store const initialState = { startedAt: undefined, stoppedAt: undefined, baseTime: undefined }; function reducer(state = initialState, action) { switch (action.type) { case "RESET_TIMER": return { ...state, baseTime: 0, startedAt: state.startedAt ? action.now : undefined, stoppedAt: state.stoppedAt ? action.now : undefined }; case "START_TIMER": return { ...state, baseTime: action.baseTime, startedAt: action.now, stoppedAt: undefined }; case "STOP_TIMER": return { ...state, stoppedAt: action.now } default: return state; } } const store = createStore(reducer);
请注意,动作创建者和缩减器仅处理原始值,并且不使用任何类型的间隔或TICK
动作类型.现在,组件可以轻松订阅此数据并根据需要随时更新:
// Helper function that takes store state // and returns the current elapsed time function getElapsedTime(baseTime, startedAt, stoppedAt = new Date().getTime()) { if (!startedAt) { return 0; } else { return stoppedAt - startedAt + baseTime; } } class Timer extends React.Component { componentDidMount() { this.interval = setInterval(this.forceUpdate.bind(this), this.props.updateInterval || 33); } componentWillUnmount() { clearInterval(this.interval); } render() { const { baseTime, startedAt, stoppedAt } = this.props; const elapsed = getElapsedTime(baseTime, startedAt, stoppedAt); return (); } } function mapStateToProps(state) { const { baseTime, startedAt, stoppedAt } = state; return { baseTime, startedAt, stoppedAt }; } Timer = ReactRedux.connect(mapStateToProps, { startTimer, stopTimer, resetTimer })(Timer);Time: {elapsed}
您甚至可以使用不同的更新频率在同一数据上显示多个计时器:
class Application extends React.Component { render() { return (); } }
你可以在这里看到一个有效的JSBin:https://jsbin.com/dupeji/12/edit?js,output
如果您打算在更大的应用程序中使用它,那么我会使用requestAnimationFrame
而不是setInterval
性能问题.当您显示毫秒时,您会在移动设备上注意到这一点,而不是在桌面浏览器上.
更新了JSFiddle
https://jsfiddle.net/andykenward/9y1jjsuz
您希望使用clearInterval
带有调用结果的函数setInterval
(唯一标识符)并停止该间隔执行任何进一步的操作.
因此,而不是声明一个setInterval
内部start()
,而是将其传递给reducer,以便它可以将其ID存储在状态:
interval
作为操作对象的成员传递给调度程序
start() { const interval = setInterval(() => { store.dispatch({ type: 'TICK', time: Date.now() }); }); store.dispatch({ type: 'START_TIMER', offset: Date.now(), interval }); }
interval
在START_TIMER
动作减速器中存储新状态
case 'START_TIMER': return { ...state, isOn: true, offset: action.offset, interval: action.interval };
______
按照渲染组件 interval
通过在interval
作为组件的属性:
const render = () => { ReactDOM.render(, document.getElementById('app') ); }
然后,我们可以检查out组件中的状态,以根据是否存在属性来呈现它interval
:
render() { return (); }Time: {this.format(this.props.time)}
______
停止计时器
要停止计时器,我们清除使用的间隔,clearInterval
然后initialState
再次应用:
case 'STOP_TIMER': clearInterval(state.interval); return { ...initialState };
______
更新了JSFiddle
https://jsfiddle.net/8z16xwd2/2/