Создание простого таймера обратного отсчета с помощью Clojure/Reagent

Я экспериментирую с Clojure и Reagent почти без опыта, пытаясь сделать простой таймер.

(defn reset-component [t]
  [:input {:type "button" :value "Reset"
           :on-click #(reset! t 60)}])

(defn countdown-component []
  (let [seconds-left (atom 60)]
    (fn []
      (js/setTimeout #(swap! seconds-left dec) 1000)
      [:div.timer
        [:div "Time Remaining: " (show-time @seconds-left)]
        [reset-component seconds-left]])))

Обратный отсчет таймера работает правильно, пока я не нажму кнопку сброса. После этого таймер начинает обратный отсчет в два раза быстрее. Каждый раз, когда я нажимаю кнопку сброса, обратный отсчет ускоряется.

Как я могу заставить таймер автоматически отсчитывать время при загрузке страницы, но не отсчитывать быстрее при нажатии кнопки сброса?


person user3571908    schedule 16.05.2015    source источник


Ответы (4)


Разыменование оставшихся секунд в компоненте сброса вызывает повторную визуализацию компонента обратного отсчета, который прикрепляет другую функцию декрементатора к вашему компоненту обратного отсчета.

person mac    schedule 16.05.2015

Для дальнейшего использования, reagent 0.6.0+ поставляется с новым макросом with-let, очень полезным для вещей, требующих надлежащего "уничтожения", таких как setInterval/clearInterval:

(defn countdown-component []
  (r/with-let [seconds-left (r/atom 60)
               timer-fn     (js/setInterval #(swap! seconds-left dec) 1000)]
        [:div.timer
          [:div "Time Remaining: " (str @seconds-left)]]
    (finally (js/clearInterval timer-fn))))

Вот официальное объявление об этой функции.

Обратите внимание, что вызов setTimeout из функции рендеринга также работает, но это не считается хорошей практикой. (например, https://clojurians.slack.com/archives/C0620C0C8/p1495568238109060)

person Aisamu    schedule 23.05.2017
comment
Я не понимаю, как таймер перестает тикать, как только он достигает нуля... - person Eric Auld; 16.01.2021

Решение, которое я нашел, состояло в том, чтобы использовать setInterval перед функцией компонента, а не setTimeout внутри функции компонента:

(defn reset-component [seconds]
  [:input {:type "button" :value "Reset"
           :on-click #(reset! seconds 60)}])

(defn countdown-component []
  (let [seconds-left (atom 60)]
    (js/setInterval #(swap! seconds-left dec) 1000)
    (fn []
      [:div.timer
       [:div "Time Remaining: " @seconds-left]
       [reset-component seconds-left]])))
person user3571908    schedule 04.06.2015

Вы, вероятно, хотите этого:

(defn reset-component [t]
  [:input {:type "button" :value "Reset"
           :on-click #(reset! t 60)}])

(defn countdown-component []
  (let [seconds-left (atom 60)]
    (js/setInterval #(swap! seconds-left dec) 1000)
    (fn []  
      [:div.timer
        [:div "Time Remaining: " (show-time @seconds-left)]
        [reset-component seconds-left]])))

Обратите внимание, что теперь вызов setInterval происходит только при инициализации компонента, а не на этапе рендеринга.

person Michiel Borkent    schedule 03.06.2015
comment
Обратите внимание, что это работает, только если вы измените setTimeout на setInterval. - person Aisamu; 23.05.2017