2016-01-03 18 views
13

Tôi đã cố gắng tạo đồng hồ bấm giờ phản ứng và truyền lại. Tôi đã gặp rắc rối khi tìm ra cách thiết kế một thứ như vậy trong quá trình redux.Tạo đồng hồ bấm giờ với thông đỏ

Điều đầu tiên bạn nghĩ đến là có hành động START_TIMER sẽ đặt giá trị offset ban đầu. Ngay sau đó, tôi sử dụng setInterval để kích hoạt lại hành động TICK tính toán lượng thời gian đã trôi qua bằng cách sử dụng bù trừ, thêm thời gian đó vào thời gian hiện tại và sau đó cập nhật offset.

Cách tiếp cận này dường như hoạt động, nhưng tôi không chắc chắn làm thế nào tôi sẽ xóa khoảng thời gian để dừng nó. Ngoài ra, nó có vẻ như thiết kế này là người nghèo và có lẽ là một cách tốt hơn để làm điều đó.

Đây là toàn bộ JSFiddle có chức năng START_TIMER hoạt động. Nếu bạn chỉ muốn xem bộ giảm tốc của tôi trông như thế nào, tại đây là:

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; 
    } 
} 

Tôi thực sự đánh giá cao sự giúp đỡ nào.

Trả lời

32

tôi có lẽ sẽ khuyên bạn nên đi về vấn đề này khác nhau: cửa hàng chỉ trạng thái cần thiết để tính toán thời gian trôi qua trong cửa hàng, và để cho các thành phần thiết riêng khoảng của họ cho tuy nhiên họ thường muốn cập nhật màn hình.

Điều này giúp hành động gửi đến mức tối thiểu - chỉ các hành động để bắt đầu và dừng (và đặt lại) hẹn giờ được gửi đi. Hãy nhớ rằng, bạn đang trả về một đối tượng trạng thái mới mỗi lần bạn gửi một hành động và mỗi thành phần ed connect ed sau đó hiển thị lại (mặc dù chúng sử dụng tối ưu hóa để tránh quá nhiều lần hiển thị lại trong các thành phần được bao bọc).Hơn nữa, nhiều công văn hành động có thể làm cho nó khó khăn để gỡ lỗi thay đổi trạng thái ứng dụng, vì bạn phải xử lý tất cả các số TICK cùng với các hành động khác.

Dưới đây là một ví dụ:

// 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); 

Thông báo những người sáng tạo hành động và giao dịch giảm chỉ với các giá trị nguyên thủy, và không sử dụng bất kỳ loại khoảng hoặc một loại TICK hành động. Bây giờ là một thành phần có thể dễ dàng đăng ký vào dữ liệu này và cập nhật thường xuyên như nó muốn:

// 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); 

Bạn thậm chí có thể hiển thị nhiều giờ trên cùng một dữ liệu với một tần số cập nhật khác nhau:

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

Bạn có thể thấy một working JSBin với thực hiện điều này ở đây: https://jsbin.com/dupeji/12/edit?js,output

+0

Tôi xin lỗi vì nhận xét muộn của tôi, nhưng cảm ơn rất nhiều! Đọc qua tất cả điều này thực sự đã giúp tôi hiểu rõ hơn về cách tôi nên cấu trúc/thiết kế tất cả những điều này. Tôi có hai câu hỏi nếu bạn không phiền. Đầu tiên là tại sao mã trên không hoạt động nếu tôi sử dụng 'null' thay vì' undefined'? Thứ hai, tôi là một chút không chắc chắn về dòng 'clearInterval (this.interval);'. 'This.interval' được định nghĩa ở đâu? Hay bạn muốn làm điều này 'this.interval = setInterval()' ở trên nó? Cảm ơn một lần nữa nó có nghĩa là rất nhiều mà bạn sẽ đi ra khỏi con đường của bạn để làm điều này! – saadq

+0

@meh_programmer Tôi đã sử dụng 'undefined' để đối số mặc định trong các tác vụ' getElapsedTime' (chuyển undefined làm cho nó sử dụng mặc định, nhưng đó không phải là trường hợp khi truyền null). Bạn nói đúng về khoảng thời gian - Tôi sẽ sửa lỗi đó! :) –

+0

nhanh Lưu ý: Có vẻ bạn có chức năng thuần túy phi trong giảm: new Date(): "Nó rất quan trọng là giảm vẫn tinh khiết Những điều bạn không bao giờ nên làm bên trong một giảm ...." Từ tài liệu https: //github.com/reactjs/redux/blob/master/docs/basics/Reducers.md Thực tiễn tốt nhất tôi nghĩ là có tất cả các tạp chất trong ActionCreators https://github.com/reactjs/redux/issues/1088 –

5

Bạn muốn sử dụng chức năng clearInterval có kết quả của cuộc gọi đến setInterval (số nhận dạng duy nhất) và dừng khoảng thời gian đó để thực hiện thêm bất kỳ.

Vì vậy, chứ không phải là khai báo một setInterval trong start(), thay vì vượt qua nó để giảm tốc để nó có thể lưu trữ ID của nó đối với nhà nước:

đèo interval để Dispatcher là một thành viên của đối tượng hành động

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

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

cửa hàng interval về trạng thái mới trong giảm START_TIMER hành động

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

______

Rendering thành phần theo interval

đèo trong interval như một thuộc tính của các thành phần:

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

Sau đó chúng tôi có thể kiểm tra nhà nước trong thành phần ra để render theo đó có thuộc tính interval hay không:

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> 
); 
} 

______

Dừng bộ đếm thời gian

Để ngừng đồng hồ đếm chúng ta rõ ràng khoảng thời gian sử dụng clearInterval và chỉ cần áp dụng các initialState một lần nữa:

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

______

Cập nhật JSFiddle

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

+0

Cảm ơn rất nhiều cho câu trả lời và giải thích! Điều này là khá nhiều những gì tôi đã cố gắng để làm. Tôi chỉ có một câu hỏi khác - liệu bạn có nói rằng thực hành không tốt để có một hành động kích hoạt một hành động khác với 'setInterval'? – saadq

+0

@meh_programmer Có thể, có. [Thảo luận này về GitHub] (https://github.com/rackt/redux/issues/184#issuecomment-115987375) dường như gợi ý rằng việc đặt logic khoảng thời gian bên trong một đối tượng 'logic nghiệp vụ' riêng biệt mà đăng ký vào cửa hàng sẽ là hợp lý hơn. – sdgluck

8

Nếu bạn đang sử dụng điều này trong một ứng dụng lớn hơn sau đó tôi sẽ sử dụng requestAnimationFrame thay vì một setInterval cho vấn đề hiệu suất. Khi bạn đang hiển thị mili giây, bạn sẽ thấy điều này trên thiết bị di động không quá nhiều trên các trình duyệt trên máy tính để bàn.

Cập nhật JSFiddle

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

+0

Vâng, đây là một phần của ứng dụng lớn hơn. Và cảm ơn rất nhiều, tôi luôn nghĩ rằng 'requestAnimationFrame' là để làm công cụ với' canvas', tôi không biết tôi có thể sử dụng nó trong tình huống như thế này. Đã bỏ phiếu! – saadq

Các vấn đề liên quan