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

标签 clojurescript reagent re-frame

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

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

另外,在使用re-frame时,我希望能够

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

我该怎么做呢?

最佳答案

大纲:

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

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

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

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

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

诀窍在于 reset! - 每当我们收到 :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) 除了 reset 之外,每次输入字段的值发生变化!-ing 试剂原子。让我们为它添加一个辅助函数:

(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. :点击
  2. (reset!int-atom) -> 重新渲染输入框
  3. (dispatch [:integer-input-field-updated new-value])
  4. (assoc db :integer-value value)
  5. 试剂 react 被触发
  6. (reset! int-atom) -> 再次重新渲染输入字段

这不是很酷。我们需要确保信息只在一个方向上流动,这意味着对 :integer-value 的更改应该不会触发对本地原子的更改,但是更改为本地原子 do 传播到 :integer-value。基本上,我们的 UI 不应该对它自己发起的更新使用react。我们可以通过在数据库中引入另一个键来实现这一点,re-frame/subscribe 到它,并强制所有未更新的 :integer-value从用户界面更新两者。让我们称这个键为 :integer-value-input-field 并为它创建一个 sub 和一个处理程序:

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

关于clojurescript - 如何通过在试剂/重新框架中键入和从代码更新相同的输入字段?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35091827/

相关文章:

semantic-ui - Clojurescript Semantic UI React Search 自定义渲染器

clojure - Clojure Pedestal 框架中的架构模型是否是类似于 Google Wave 的操作转换?

clojure - 自动刷新/自动重新加载资源

reactjs - Clojurescript、Reagent : pass atoms down as inputs, 或用作全局变量?

electron - 尝试将 re-frame-10x (是 re-frame-trace)与 descjop ( Electron )项目一起使用

ClojureScript。重新渲染时重置 Reagent 中的原子

clojure - Clojurescript/Clojure 中服务器和客户端之间的代码共享

data-structures - 表示无冗余树路径的数据结构

clojure - 试剂中的交互式列表

clojurescript - 试剂 react Clojurescript 警告 : Every element in a seq should have a unique :key