如何在reagent/re-frame中通过键入和代码更新相同的输入字段?

3
我有一个试剂组件,其中包含一个整数输入字段和两个按钮“+1”和“-1”。我希望用户能够:
  • 在输入框中直接输入整数值
  • 点击“+1”按钮将输入框中的值增加1
  • 点击“-1”按钮将输入框中的值减少1
此外,当使用re-frame时,我希望能够:
  • 在输入或通过单击其中一个按钮进行调整后,将该值保存到re-frame的应用程序数据库中
  • 如果在re-frame的应用程序数据库中更改了输入字段的值(例如,在从某些API服务器获取值之后),则更新输入字段的值
我该如何做到这一点?
1个回答

6

概述:

  • 在组件内部使用一个定义局部变量来保存整数值
  • :on-change:on-click中通过reset!重置值

为了与re-frame的应用程序数据库集成,我们需要添加以下内容:

  • :on-change:on-click中调用re-frame/dispatch
  • 通过调用re-frame/subscribe,将值存储到re-frame的应用程序数据库中,并相应地更新本地reagent原子

现在我们来看一些代码,从reagent组件开始:

(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)}]])))

关键在于每次得到:on-change事件时重置原子,让reagent/react在幕后重新渲染字段。另外请注意,: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))))

这段代码可以直接使用。但是,如果我们决定将整数值存储在re-frame中,则需要更多的代码。首先,让我们创建一个处理程序,在其中将从输入字段接收到的值存储在数据库中:

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

现在我们需要每次输入字段的值更改时调用(re-frame/dispatch :integer-input-field-updated value),以及重置reagent原子。让我们添加一个辅助函数来完成这个任务:
(defn- store-int-value [int-atom value]
  (reset! int-atom value)
  (re-frame/dispatch :integer-input-field-updated value))

我们现在需要每次准备存储值时都调用这个函数,而不是 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

让我们回顾一下我们到目前为止所做的事情:
- 输入字段和两个按钮,可以更改输入字段中的值 - 一个试剂原子,存储值并将UI元素绑定在一起 - 存储该值的re-frame应用程序数据库中的位置
我们需要一种支持另一个方向上的值流动的方法,即从应用程序数据库流到本地原子。我们可以连接一个订阅,以对(: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,并强制所有不来自UI的:integer-value更新同时更新该键。让我们称这个键为: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)))

最后,让我们重构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)}]])))

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

(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)}]])))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接