使用 Clojure/Reagent 制作一个简单的倒数计时器

Making a simple countdown timer with 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]])))

在我按下重置按钮之前,计时器倒计时似乎正常工作。之后,计时器开始以两倍的速度倒计时。每次我按下重置按钮时,倒计时都会更快。

如何让计时器在页面加载时自动倒计时,而不是在单击重置按钮时倒计时更快?

取消引用 reset-component 中的 seconds-left 会触发 countdown 组件的重新渲染,这会将另一个递减器函数附加到您的 countdown-component。

你可能想要这个:

(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 的调用仅在组件初始化时发生,而不是在渲染阶段发生。

我找到的解决方案是在组件函数之前使用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]])))

为了将来参考,reagent 0.6.0+ 附带了一个新的 with-let 宏,对于需要适当 "destruction" 的东西非常有用,例如 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))))

这是该功能的官方 anouncement

请注意,从渲染函数调用 setTimeout 也可以,但不被视为良好做法。 (例如 https://clojurians.slack.com/archives/C0620C0C8/p1495568238109060