2016-01-03 46 views
13

我一直在嘗試製作一個秒錶反應和redux。我一直在想辦法解決如何用redux來設計這樣的事情。用redux創建秒錶

首先想到的是有一個START_TIMER行動,將設置初始值offset值。在此之後,我使用setInterval來反覆啓動一個TICK動作,它會使用偏移量計算已經過了多少時間,將其添加到當前時間,然後更新offset

這種方法似乎有效,但我不知道如何清除間隔來阻止它。而且,這種設計看起來很差,可能有更好的方法來做到這一點。

這裏是一個完整的JSFiddleSTART_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; 
    } 
} 

我真的很感激任何幫助。

回答

32

我可能會建議要對此有所不同:只存儲所需要的狀態,計算在店裏消耗的時間,並讓組件設置其自己間隔但是他們經常希望更新顯示。

這將操作調度保持在最低限度 - 僅調度啓動和停止(以及重置)計時器的操作。請記住,您每次都會返回一個新的狀態對象您分派一個操作,然後每個connect ed組件重新呈現(儘管它們使用優化以避免在被包裝的組件內進行太多重新呈現)。此外,很多許多操作調度都可能使調試應用程序狀態更改變得困難,因爲您必須處理所有的TICK以及其他操作。

下面是一個例子:

// 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 (
     <div> 
     <div>Time: {elapsed}</div> 
     <div> 
      <button onClick={() => this.props.startTimer(elapsed)}>Start</button> 
      <button onClick={() => this.props.stopTimer()}>Stop</button> 
      <button onClick={() => this.props.resetTimer()}>Reset</button> 
     </div> 
     </div> 
    ); 
    } 
} 

function mapStateToProps(state) { 
    const { baseTime, startedAt, stoppedAt } = state; 
    return { baseTime, startedAt, stoppedAt }; 
} 

Timer = ReactRedux.connect(mapStateToProps, { startTimer, stopTimer, resetTimer })(Timer); 

你甚至可以用不同的更新頻率顯示相同數據的多個計時器:

class Application extends React.Component { 
    render() { 
    return (
     <div> 
     <Timer updateInterval={33} /> 
     <Timer updateInterval={1000} /> 
     </div> 
    ); 
    } 
} 

你可以看到一個working JSBin與這個實施在這裏:https://jsbin.com/dupeji/12/edit?js,output

+0

我對我已故的評論表示歉意,但非常感謝!通過閱讀所有這些,真的幫助我更好地理解如何構建/設計所有這些。如果你不介意,我有兩個問題。首先是爲什麼上面的代碼不工作,如果我使用'null'而不是'undefined'?其次,我對'clearInterval(this.interval)'這一行有點不確定。 'this.interval'定義在哪裏?還是你的意思是在它上面執行'this.interval = setInterval()'?再次感謝你的意思很多,你會竭盡全力去做到這一點! – saadq

+0

@meh_programmer我使用了'undefined',所以'getElapsedTime'中的默認參數起作用(傳遞undefined使其使用默認值,但傳遞null時不是這種情況)。你是對的 - 我會解決的! :) –

+0

快速注:看來你有減速非純函數:新的Date():「這是非常重要的是,減速保持純粹的東西,你不應該做機內部......。」從文檔 的https: //github.com/reactjs/redux/blob/master/docs/basics/Reducers.md 我認爲最佳實踐是在ActionCreators中擁有所有的雜質 https://github.com/reactjs/redux/issues/1088 –

5

您想要使用clearInterval函數,該函數將調用setInterval(唯一標識符)的結果並停止執行任何進一步的時間間隔。

因此,而不是聲明一個setIntervalstart(),而不是將它傳遞給減速機,以便它可以存儲關於狀態其ID:

通行證interval到調度器作爲動作對象的成員

start() { 
    const interval = setInterval(() => { 
    store.dispatch({ 
     type: 'TICK', 
     time: Date.now() 
    }); 
    }); 

    store.dispatch({ 
    type: 'START_TIMER', 
    offset: Date.now(), 
    interval 
    }); 
} 

新的狀態存儲intervalSTART_TIMER動作減速

case 'START_TIMER': 
    return { 
    ...state, 
    isOn: true, 
    offset: action.offset, 
    interval: action.interval 
    }; 

______

根據interval

操作渲染的成分在interval作爲組件的屬性:

const render =() => { 
    ReactDOM.render(
    <Timer 
     time={store.getState().time} 
     isOn={store.getState().isOn} 
     interval={store.getState().interval} 
    />, 
    document.getElementById('app') 
); 
} 

然後我們就可以檢查出組件內的狀態來呈現它根據是否有財產interval與否:

render() { 
    return (
    <div> 
     <h1>Time: {this.format(this.props.time)}</h1> 
     <button onClick={this.props.interval ? this.stop : this.start}> 
     { this.props.interval ? 'Stop' : 'Start' } 
     </button> 
    </div> 
); 
} 

______

停止計時器

要使用clearInterval停止我們清除間隔定時器和簡單地套用再次initialState

case 'STOP_TIMER': 
    clearInterval(state.interval); 
    return { 
    ...initialState 
    }; 

______

更新的jsfiddle

https://jsfiddle.net/8z16xwd2/2/

+0

非常感謝答案和解釋!這幾乎是我想要做的。我還有另外一個問題 - 你是否認爲有一個行爲可以通過'setInterval'觸發另一個行爲是不好的做法? – saadq

+0

@meh_programmer可能,是的。 [關於GitHub的討論](https://github.com/rackt/redux/issues/184#issuecomment-115987375)似乎表明,將區間邏輯放置在訂閱商店的單獨的「商業邏輯」對象內部更明智。 – sdgluck

8

如果你要使用這個在一個更大的應用程序,然後我會用​​而不是setInterval的性能問題。正如你所展示的毫秒,你會發現在移動設備上這不是在桌面瀏覽器上這麼多。

更新的jsfiddle

https://jsfiddle.net/andykenward/9y1jjsuz

+0

是的,這是一個更大的應用程序的一部分。並且非常感謝,我一直認爲'requestAnimationFrame'是爲了用'canvas'做東西,我不知道我可以在這種情況下使用它。 Upvoted! – saadq