如何通过在试剂/重新框架中键入和来自代码来更新相同的输入字段?

pbk*_*hrv 3 clojurescript reagent re-frame

我有一个带有整数输入字段和两个按钮“+1”和“-1”的试剂组件。我想让用户能够:

  • 直接在输入字段中输入整数值
  • 单击“+1”将输入字段中的值增加 1
  • 单击“-1”将输入字段中的值减 1

另外,在使用重新框架时,我希望能够

  • 通过单击其中一个按钮输入或调整值后,将值保存到重新构建的应用程序数据库
  • 如果输入字段的值在 re-frame 的应用程序数据库中发生变化,则更新它的值(例如,在我们从某个 API 服务器获取值之后)

我该怎么做?

pbk*_*hrv 5

总纲:

  • 使用在组件内部局部定义的试剂原子来保存整数值
  • reset!中的值:on-change:on-click

为了与 re-frame 的 app db 集成,我们需要添加以下内容:

  • 调用re-frame/dispatch:on-change:on-click
  • re-frame/subscribe 值存储在重新框架的应用程序数据库中的位置并相应地更新本地试剂原子

让我们看一些代码,从试剂组件开始:

(defn integer-field [default-value]
  (let [int-atom (atom default-value)]
    (fn []
      [:div
       [:input {:type "text"
                :value @int-atom
                :on-change #(reset! int-atom (-> % .-target .-value))}]
       [:button {:on-click #(adjust-int int-atom 1)} "+"]
       [:button {:on-click #(adjust-int int-atom -1)}]])))
Run Code Online (Sandbox Code Playgroud)

诀窍是reset!每次我们收到一个:on-change事件时都在原子中,让试剂/反应负责重新渲染幕后的场。

另请注意,:on-click调用另一个adjust-int增加/减少字段原子值的函数。这里是:

(defn- adjust-int [int-atom delta]
  (let [v (js/parseInt @int-atom)
        valid? (not (js/Number.isNaN v))
        new-v (+v delta)]
    (when valid?
      (reset! int-atom new-v))))
Run Code Online (Sandbox Code Playgroud)

这按原样工作。但是,如果我们决定在重新帧中存储整数值,我们确实需要更多代码。首先,让我们创建一个处理程序来存储它从数据库中的输入字段接收的值:

(re-frame/register-handler
  :integer-input-field-updated
  (fn [db [_ value]]
    (assoc db :integer-value value)))
Run Code Online (Sandbox Code Playgroud)

(re-frame/dispatch :integer-input-field-updated value)除了reset!-ing 试剂原子之外,我们现在需要在每次输入字段的值发生变化时调用。让我们为它添加一个辅助函数:

(defn- store-int-value [int-atom value]
  (reset! int-atom value)
  (re-frame/dispatch :integer-input-field-updated value))
Run Code Online (Sandbox Code Playgroud)

我们现在需要调用这个函数,而不是reset!每次值准备好存储时调用。让我们adjust-int首先进行更改:

(defn- adjust-int [int-atom delta]
  (let [v (js/parseInt @int-atom)
        valid? (not (js/Number.isNaN v))
        new-v (+v delta)]
    (when valid?
      (store-int-value int-atom new-v)))) ;; <---- changed
Run Code Online (Sandbox Code Playgroud)

让我们回顾一下到目前为止的内容:

  • 输入字段和两个更改输入字段中值的按钮
  • 一个试剂原子,用于存储值并将 UI 元素联系在一起
  • re-frame 的 app db 中存储值的位置

我们需要一种方法来支持另一个方向的值流,应用程序数据库本地原子。我们可以连接一个订阅,对更改做出反应(:integer-value @db)并重置我们的本地原子。等等,这听起来像是某种循环,不是吗?让我们可视化流程:

  1. :on-click
  2. (reset! int-atom) -> 重新渲染输入字段
  3. (dispatch [:integer-input-field-updated new-value])
  4. (assoc db :integer-value value)
  5. 试剂反应被触发
  6. (reset! int-atom) -> 重新渲染输入字段

那不酷。我们需要确保信息只在一个方向流动,这意味着改变:integer-value应该不会触发更改本地原子,但切换到本地原子得到传播到:integer-value。基本上,我们的 UI 不应该对它自己发起的更新做出反应。我们可以通过在 db 中引入另一个键来实现这re-frame/subscribe一点,并强制所有:integer-value不是来自 UI 的更新都更新。让我们调用这个键:integer-value-input-field并为其创建一个子项和一个处理程序:

(re-frame/register-sub
  :integer-field-input-value
  (fn [db _]
    (reaction (:integer-field-input-value @db))))

(re-frame/register-handler
  :integer-value-updated   ;; <--- for use by other parts of the app
  (fn [db [_ value]]
    (assoc db :integer-field value :integer-field-input-value value)))
Run Code Online (Sandbox Code Playgroud)

最后,让我们重构integer-field组件:

(defn integer-field [default-value]
  (let [int-atom (atom default-value)
        the-int (re-frame/subscribe [:integer-field-input-value])] ;; <--- source of truth
    (fn []
      (reset! int-atom @the-int) ;; <--- spread the truth
      [:div
       [:input {:type "text"
                :value @int-atom
                :on-change #(store-int-value int-atom(-> % .-target .-value))}]
       [:button {:on-click #(adjust-int int-atom 1)} "+"]
       [:button {:on-click #(adjust-int int-atom -1)}]])))
Run Code Online (Sandbox Code Playgroud)

现在,所有代码都集中在一个地方......

(re-frame/register-sub
  :integer-field-input-value
  (fn [db _]
    (reaction (:integer-field-input-value @db))))

;; to be used by other parts of the app
(re-frame/register-handler
  :integer-value-updated
  (fn [db [_ value]]
    (assoc db :integer-field value :integer-field-input-value value)))

;; to be used by the integer-field component
(re-frame/register-handler
  :integer-input-field-updated
  (fn [db [_ value]]
    (assoc db :integer-value value)))

(defn- store-int-value [int-atom value]
  (reset! int-atom value)
  (re-frame/dispatch :integer-input-field-updated value))

(defn- adjust-int [int-atom delta]
  (let [v (js/parseInt @int-atom)
        valid? (not (js/Number.isNaN v))
        new-v (+v delta)]
    (when valid?
      (store-int-value int-atom new-v))))

(defn integer-field [default-value]
  (let [int-atom (atom default-value)
        the-int (re-frame/subscribe [:integer-field-input-value])]
    (fn []
      (reset! int-atom @the-int)
      [:div
       [:input {:type "text"
                :value @int-atom
                :on-change #(store-int-value int-atom(-> % .-target .-value))}]
       [:button {:on-click #(adjust-int int-atom 1)} "+"]
       [:button {:on-click #(adjust-int int-atom -1)}]])))
Run Code Online (Sandbox Code Playgroud)