August 29, 2019
By: 杨柳

ClojureScript表单简单实用指南

  1. 单页面表单,简单逻辑
  2. 需要注意的点:
  3. 代码示例

单页面表单,简单逻辑

关于表单提交的最复杂的逻辑,无非包括有以下几种情况: a. 查询表单可初始化数据的接口, 结果分为两种情况,没有可初始化的数据,表单有可初始化的数据(之前提交过的再修改) b. 修改表单 有初始值时需要填入,没有初始值时需要清空 c. 重置表单 d. 表单验证 e. 表单提交

需要注意的点:

查询表单可初始化数据的接口 db中此初始值设置为nil, 并且每次调用接口前, 也需要将其重置为nil, 以免出现没有可用结果返回表单不初始化的问题。 接口调用成功后,将db设置为从接口返回中取值,初始值 {:key1 nil} 定义操作更新数据的 event handler/effect handler

(rf/reg-event-db
 :update-key1
  (fn [db result]
   (assoc-in db [:key1] result)))

也可以这样定义:

(rf/reg-event-fx
 :update-key1
  (fn [cofx [result]]
   (let [db (:db cofx)]
    {:db (assoc-in db [:key1] result)})))

清除时直接调用(rf/dispatch [:update-key1 nil])

有时一个按钮操作需要同时执行多个请求,并且有某个接口的参数对其他接口的返回结果有依赖 定义调用接口的 event handler/effect handler

(rf/reg-event-fx
 :search-key1
  (fn [cofx [_ param]]
   (let [db (:db cofx)]
    {:db (assoc-in db [:key1] nil)
     :http-xhrio {:uri "url" 
                  :method :get 
                  :params param
                  ...
                  :on-success [:update-key2]
                  }})))

在实际开发过程中,我经常会遇到reagent中组件的刷新问题, react中的render, github地址 https://github.com/reagent-project/reagent 在此截取某一段 State is handled using Reagent's version of atom, like this:

(defonce click-count (r/atom 0))

(defn state-ful-with-atom []
  [:div {:on-click #(swap! click-count inc)}
   "I have been clicked " @click-count " times."])

Any component that dereferences a reagent.core/atom will be automatically re-rendered.

If you want do some setting up when the component is first created, the component function can return a new function that will be called to do the actual rendering:

(defn timer-component []
  (let [seconds-elapsed (r/atom 0)]
    (fn []
      (js/setTimeout #(swap! seconds-elapsed inc) 1000)
      [:div
       "Seconds Elapsed: " @seconds-elapsed])))

This way you can avoid using React's lifecycle callbacks like getInitialState and componentWillMount most of the time

代码示例

此代码可参考之处无非是: form表单中的项根据指定item动态生成 表单的验证、提交和重置,都使用antd的方法 表单Input无法输入中文的问题 其实这个问题还是控件render的问题,有些写法,导致每次input输入时会使得整个表单重绘,就会遇到只能输入英文字符,或者输入一个字符就失去焦点的问题(错误的示例正是此问题) 当然,如果你在写表单相关或者还未入手,可以参考此段代码,看看代码,同时找找问题,一起进步~

(ns size-measure.test
  (:require
   [reagent.core :as r :refer [atom]]
   [re-frame.core :as rf]
   [ajax.core :as http]
   [day8.re-frame.http-fx]
   [size-measure.utils :as utils]
   ["antd" :as ant]))

(defn- concat-key [keys]
  (concat [:test] keys))

(defn- data->db [db keys value]
  (assoc-in db (concat-key keys) value))

(defn- get-value-from-db [db keys]
  (get-in db (concat-key keys)))

(rf/reg-event-db
 :test-init
 (fn [_ _]
   {:test {:api-result nil
           :api-result-2 nil}}))

(rf/reg-event-db
 :search-api-result
 (fn [db _]
   (let [random-n (int (rand 100))
         init-result {:key1 (str "val1+" random-n)}
         result (if (> 50 random-n) nil init-result)]
     (data->db db [:api-result] result))))

(rf/reg-event-db
 :set-api-result-2
 (fn [db [_ result2]]
   (data->db db [:api-result-2] {:key-test-2 (:key-test result2)
                                 :key2-test-2 (:key2-test result2)})))

;;返回map, 请求多个接口,并且第二个接口对第一个接口返回结果有依赖
(rf/reg-event-fx
 :search-api-result-multi
 (fn [cofx [_ _]]
   (let [random-n (fn [] (rand-int 100))
         init-result (fn [value]
                       {:key-test (str "1-val-" value)
                        :key2-test (str "2-val-" value)}
                       )
         result (fn [] (let [value (random-n)] (if (> 50 value) nil (init-result value))))
         ]
     {:db (data->db (:db cofx) [:api-result] (result))
      :dispatch [:set-api-result-2 (result)]})))

(rf/reg-event-fx
 :submit-form
 (fn [cofx [_ form-datas]]
   (prn "form-datas:>>>" form-datas)
   (js/alert (str "你填了" (clojure.string/join ", " (vals form-datas))))
   (let []
     {})))

(rf/dispatch-sync [:test-init])

(rf/reg-sub
 :api-result
 (fn [db _] (-> db :test :api-result)))

(rf/reg-sub
 :api-result-2
 (fn [db _] (-> db :test :api-result-2)))

(def api-result-data (rf/subscribe [:api-result]))
(def api-result-data-2 (rf/subscribe [:api-result-2]))

(def form-items [{:name "1表单项1" :form-key :key-test :form-name "keyTest"}
                 {:name "1表单项2" :form-key :key2-test :form-name "key2Test"}])
(def form-items-2 [{:name "2表单项1" :form-key :key-test-2 :form-name "keyTest2"}
                   {:name "2表单项2" :form-key :key2-test-2 :form-name "key2Test2"}])


(def FormItem (.-Item ant/Form))
(defn test-form-1 []
  (let []
    (prn ">>>>load 1...")
    (fn [props]
      (prn ">>>>load 2...")
      (let [the-form (utils/get-form)
            {:keys [resetFields
                    validateFields]} the-form
                                        ;form-data (r/atom @api-result-data)
            form-rows (for [item form-items]
                        [:> FormItem {:label (:name item)}
                         (utils/decorate-field
                          the-form (:form-name item)
                          {:initialValue (if (nil? @api-result-data) "" ((-> item :form-key) @api-result-data))
                           :rules [{:required true}]
                           }
                          [:> ant/Input {:type "text"
                                         :name (:form-name item)
                                         :placeholder (str "请填写" (:name item))
                                         :on-change (fn [e] (.preventDefault e))}]
                          )])
            form-rows-div (fn [] (into [:div ] form-rows))
            ]
        (prn ">>>>load 3....")
        [:div
         [:> ant/Form {:layout "horizontal"
                       :on-submit (fn [e]
                                    (.preventDefault e)
                                    #_((-> the-form :validateFields)
                                       (fn [err values]
                                         (if (not err)
                                           (do (prn ">>>form values" values)
                                               (rf/dispatch
                                                [:submit-form (js->clj values :keywordize-keys true)])
                                               ))))
                                    (validateFields
                                     (fn [err values]
                                       (if (not err)
                                         (rf/dispatch [:submit-form (js->clj values :keywordize-keys true)])))))}
          [:div {:style {:margin-top 10 :color "red"}}
           [:div "错误示例"]]
          [form-rows-div]
          [:div {:style {:margin-top 10 :color "red"}}
           [:div "正确示例"]]
          (into [:div ] (for [item form-items-2]
                          [:> FormItem {:label (:name item)}
                           (utils/decorate-field
                            the-form (:form-name item)
                            {:initialValue (if (nil? @api-result-data-2) "" ((-> item :form-key) @api-result-data-2))
                             :rules [{:required true}]
                             }
                            [:> ant/Input {:type "text"
                                           :name (:form-name item)
                                           :placeholder (str "请填写" (:name item))
                                           :on-change (fn [e] (.preventDefault e))}]
                            )]))
          [:> FormItem
           [:> ant/Button {:type "default"
                                        ;:on-click #(:resetFields the-form)
                           :on-click #(resetFields)
                           }
            "重置"]
           [:> ant/Button {:type "primary"
                           :html-type "submit" }
            "提交"]]]
         ]))))

(defn test-div []
  [:div
   [:div
    [:> ant/Button
     {:type "primary"
      :on-click #(rf/dispatch [:search-api-result-multi])}
     "点我,从接口初始化表单"]]
   (utils/create-form test-form-1)])

(defn ^:dev/after-load mount-root []
  (r/render [:div [test-div]]
            (.getElementById js/document "app")))

(defn init! []
  (mount-root))
Tags: clojure clojurescript