Knockout에서 만드는 카운트 다운 타이머 (컴퓨팅의 훌륭함)

Knockout 을 사용하여 카운트다운 타이머를 만듭니다.
  • 버튼을 누르면 카우트 다운이 시작됩니다
  • 초당 남은 시간 빼기
  • 버튼을 다시 누르면 카운트 다운이 중단됩니다

  • 스크린샷





    디자인



    도메인 모델을 공유하는 애플리케이션이 없기 때문에 모델에는 제약이 없습니다.
    모델에 제약이 없기 때문에 반드시 모델과 뷰 모델을 분리할 필요는 없습니다.
    여기에서는 공부를 위해 MVVM에 따라 모델과 뷰 모델을 나눕니다.

    모델



    타이머
  • 남은 시간(밀리초)
  • 감산 처리

  • 를 잡습니다.

    뷰 모델



    크게 세 가지 작업이 있습니다.

    표시용 속성


  • 남은 시간 레이블
  • 시작/중지 버튼 레이블( 開始 또는 中止 )

  • 표시 상태 및 갱신 처리


  • 남은 시간
  • 타이머의 시작 종료 상태
  • 타이머 카운트

  • 이벤트 핸들러


  • 카운트 다운 ON/OFF 전환

  • 보기


  • 남은 시간(초)
  • 시작/중지 버튼

  • 을 표시합니다.

    구현



    모델



    남은 시간과 감산 처리를 구현합니다.
    보통 JavaScript의 객체입니다.

    남은 시간은 밀리초 단위로 관리합니다.
    초기값은 10초로 했습니다.
    model = {
        restTime: 10000,
        decrement(ms) {
            this.restTime -= ms
        }
    }
    

    뷰 모델



    표시용 속성



    남은 시간



    observable 을 사용하여 변경 사항을 모니터링할 수 있습니다.
    this.restTime = ko.observable(model.restTime)
    

    남은 시간 라벨



    모델의 남은 시간은 밀리초 단위이지만 화면에 초 단위로 표시됩니다.
    computed 을 사용하여 this.restTime 의 값을 초 단위로 변환하는 observable 를 만듭니다.
    this.restTimeLabel = ko.computed(() => this.restTime() / 1000)
    

    버튼 라벨



    computed을 사용하여 내부 상태 (bool 값)를 표시 용으로 변환하는 observable를 만듭니다.
    this.toggleSwitchLabel = ko.computed(() => this._isStop() ? '開始' : '中止')
    
    _isStop 는 상태 판정용의 내부 함수입니다.
    _isStop() {
        return this.state() === STATE_STOP
    }
    
    computed 는 함수 호출의 대상인 observable도 찾을 수 있습니다.

    타이머의 시작 종료 상태


    this.toggleSwitchLabel 에서 참조하는 observable 입니다.
    this.state = ko.observable(STATE_STOP)
    

    이벤트 핸들러



    카운트다운 ON/OFF 전환
    toggle() {
        if (this._isStop()) {
            this.state(STATE_RUN)
        } else {
            this.state(STATE_STOP)
        }
    }
    
    this.state 또한 observable입니다. 이 상태 변경을 감시해, 카운트 처리의 개시·종료를 제어합니다.

    타이머 카운트 처리


    subscribe 메서드를 사용하여 this.state의 상태 변경을 모니터링합니다.

    정지 상태가 되었을 때, window.clearInterval 로 카운트다운을 정지합니다.

    시작 상태가 되었을 때, window.setInterval (을)를 사용해 1초 마다 다음의 처리를 합니다.
  • 모델을 감산
  • this.restTime 에 모델 값을 반영
  • let intervalID = null
    this.state.subscribe(() => {
        if (this._isStop()) {
            if (intervalID) {
                window.clearInterval(intervalID)
                intervalID = null
            }
        } else {
            intervalID = window.setInterval(() => {
                this.model.decrement()
                this.restTime(this.model.restTime)
            }, 1000)
        }
    })
    

    이 타이머는 자동으로 멈추지 않습니다.

    보기



    남은 시간과 시작 버튼을 표시합니다.
    <span data-bind="text: restTimeLabel">100</span><button data-bind="text: toggleSwitchLabel, click: toggle">開始</button>
    

    소스 코드 전문



    CountDown.html
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-debug.js"></script>
    
    <body>
        <span data-bind="text: restTimeLabel">100</span><button data-bind="text: toggleSwitchLabel, click: toggle">開始</button>
        <script>
            const STATE_STOP = false,
                STATE_RUN = true,
                model = {
                    restTime: 10000,
                    decrement(ms) {
                        this.restTime -= ms
                    }
                }
    
            class TimerViewModel {
                constructor(model) {
                    this.model = model
                    this.restTime = ko.observable(model.restTime)
                    this.state = ko.observable(STATE_STOP)
    
                    let intervalID = null
                    this.state.subscribe(() => {
                        if (this._isStop()) {
                            if (intervalID) {
                                window.clearInterval(intervalID)
                                intervalID = null
                            }
                        } else {
                            intervalID = window.setInterval(() => {
                                this.model.decrement(1000)
                                this.restTime(this.model.restTime)
                            }, 1000)
                        }
                    })
    
                    this.restTimeLabel = ko.computed(() => this.restTime() / 1000)
                    this.toggleSwitchLabel = ko.computed(() => this._isStop() ? '開始' : '中止')
                }
                toggle() {
                    if (this._isStop()) {
                        this.state(STATE_RUN)
                    } else {
                        this.state(STATE_STOP)
                    }
                }
                _isStop() {
                    return this.state() === STATE_STOP
                }
            }
    
            //main
            ko.applyBindings(new TimerViewModel(model))
        </script>
    </body>
    

    감상



    computed가 기분 좋다.
    ReactiveProperty 과 사용감이 비슷합니다.

    관련 기사



    INotifyPropertyChanged 구현의 불가능한 번거로움과 ReactiveProperty의 놀라운 훌륭함

    좋은 웹페이지 즐겨찾기