概述:
- 在组件内部使用一个定义局部变量来保存整数值
- 在
: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))))
让我们回顾一下我们到目前为止所做的事情:
- 输入字段和两个按钮,可以更改输入字段中的值
- 一个试剂原子,存储值并将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
(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])]
(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)}]])))
现在,所有的代码都在一个地方...
(re-frame/register-sub
:integer-field-input-value
(fn [db _]
(reaction (:integer-field-input-value @db))))
(re-frame/register-handler
:integer-value-updated
(fn [db [_ value]]
(assoc db :integer-field value :integer-field-input-value value)))
(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)}]])))