September 12, 2019
By: 杨鲁鹏

ant design form表单的使用

  1. ant design form表单的使用
    1. React的原生用法
    2. clojure用法示例
    3. 布局问题
    4. 值的初始化和校验规则设置
    5. 联动赋值
    6. 重置表单
    7. 提交
    8. 数据转换问题

ant design form表单的使用

在表单页面中,如果表单元素较多,那么各项元素值的绑定、初始化、修改更新,尤其是提交前的校验和提示,就会变得尤为繁杂,而使用ant design中的form表单则可以使我们处理起来简洁高效、清晰明了。

React的原生用法

在介绍clojure用法之前我先附上form组件使用React的原生的写法,便于后边我们的对比与理解。

import { Form, Input } from 'antd';

@Form.create()

class UserForm extends React.PureComponent {
  
  handleChangeGender = (value) => {
    const { form: { setFieldsValue } } = this.props;
    setFieldsValue({ userName: value });
  };

  onClickSubmit = () => {
    const { form } = this.props;
    form.validateFields((err, values) => {
      if (!err) {
        // 把values提交给接口
      }
    });
  };

  renderFormView() {
    const {
      form: { getFieldDecorator },
      userForm: { data },
    } = this.props;
    return (
      <Form>
        <Form.Item {...formItemLayout} label="用户名">
          {getFieldDecorator('userName', {
            initialValue: data.userName || '',
            rules: [{ required: true, message: '请输入用户名' }],
          })(<Input placeholder="请输入用户名" />)}
        </Form.Item>
      </Form>
    );
  }
}

从上面的示例中可以看出,主要是form的creategetFieldDecoratorvalidateFields方法的使用。

而关于前两个方法之前大神已经在utils中写了三个工具方法便于我们的使用:create-formdecorate-fieldget-form(该方法用于获取当前创建的form变量在后续的decorate-field方法中使用)。


clojure用法示例

由于示例代码之间的联系比较紧密,不便拆分单独演示,所以就直接把代码一块贴出来了,下边再分步讲解。

(ns custom-manage.user.user-form-views
  (:require
   ["antd" :as ant]
   [custom-manage.common.utils :as utils]))

(def FormItem (.-Item ant/Form))
(def SelectOption (.-Option ant/Select))

(def user-detail (rf/subscribe [:user-sub/user-form]))

(defn user-form-view []
  (utils/create-form
   (fn [props]
     (let [form (utils/get-form)
           {:keys [resetFields
                   setFieldsValue
                   validateFieldsAndScroll]} form]
       
       [:> ant/Form
        [:> FormItem {:label "性别:"
                      :labelCol {:span 6}
                      :wrapperCol {:span 10}}
         (utils/decorate-field
          form "user_gender"
          {:initialValue (:user_gender @user-detail)
           :rules [{:required true :message "请选择性别"}]}
          [:> ant/Select {:placeholder "请选择性别"
                          :onChange (fn [value]
                                      (if (= "man" value)
                                        (setFieldsValue (clj->js {:user_name "先生"}))
                                        (setFieldsValue (clj->js {:user_name "女士"}))))}
           [:> SelectOption {:key "man"} "男"]
           [:> SelectOption {:key "woman"} "女"]])]
        
        [:> FormItem {:label "用户名:"
                      :labelCol {:span 6}
                      :wrapperCol {:span 10}}
         (utils/decorate-field
          form "user_name"
          {:initialValue (:user_name @user-detail)
           :rules [{:required true :message "请输入用户名"}]}
          [:> ant/Input {:placeholder "请输入用户名"}])]

        [:> FormItem {:wrapperCol {:offset 10}}
         [:> ant/Button {:type "danger"
                         :on-click (fn []
                                     (resetFields))} "重置"]]
        
        [:> FormItem {:wrapperCol {:offset 10}}
         [:> ant/Button {:type "primary"
                         :on-click (fn []
                                     (validateFieldsAndScroll
                                      (fn [err values]
                                        (prn "values---" values)
                                        (when (not err)
                                          (rf/dispatch [:user/submit-userInfo
                                                          (js->clj values :keywordize-keys true)])))))} "提交"]]]))))

示例比较简单,场景就是一个性别选择器,一个用户名输入框,并且选择完性别后会自动级联在用户名输入框中填充“先生”或“女士”。最后是一个重置和提交的按钮。

场景很简单,但是基本展示了form的常用方法:

  • 布局
  • 初始化
  • 校验规则
  • 联动赋值
  • 重置
  • 提交

布局问题

表单布局主要分为三种:横向、竖向、行内 比较简单,主要是属性的设置,详情请参考表单布局

值的初始化和校验规则设置

  • 这两项主要是依靠form的getFieldDecorator方法也就是工具类方法decorate-field的第三个参数options进行配置initialValuerules。 关于校验,除了刚才最基础的用法外,还可以自定义校验

  • 当需要封装、复用或者通过数据生成FormItem时,请使用 custom-manage.common.utils下的decorate-field进行双向绑定。 并且保证其第一个参数的form是在create-form参数中通过get-form获取到的。

联动赋值

  • 在某些场景下,设置某个表单后,同时需要联动修改另一个相关表单的值,这就需要setFieldsValue方法来实现。

注意:该函数是form提供的js函数,并且只要是antd函数,通props暴露出来的,都是js函数,它的参数需要用 clj->js转一下。(setFieldsValue (clj->js {:user_name "先生"}))

  • 联动的复杂情况是,当改了表单值之后,需要根据变化的值进行业务处理(比如:复选框里包含全选选项时,切换全选选项要处理整个CheckboxGroup选项的选中状态)。 这种情况建议使用decorate-field的第三个参数optionsnormalize属性。例如:
(utils/decorate-field
             form "craft_ids"
             {:initialValue (:craft_ids @default-craft)
              :rules [{:required true :message "请选择工艺"}]
              :normalize (fn [value _]
                           ;; 2019/12/3 alisa 同一工艺的叶子节点互斥校验
                           (let [ids (js->clj value)
                                 is-parent? (fn [s id] (some #(-> % :craftwork_id (compare id) zero?) (:children s)))
                                 parent-map (reduce
                                             (fn [m id]
                                               (let [parent-nodes (specter/select (specter/walker #(is-parent? % id)) @category-crafts)
                                                     parent-id (-> parent-nodes first :craftwork_id)]
                                                 (if (contains? m parent-id) m (conj {parent-id id} m))))
                                             {}
                                             (reverse ids))
                                 select-ids-js (clj->js (vals parent-map))]
                                        ; (rf/dispatch [:set-default-craft/select-craft-ids (vals parent-map)])
                             select-ids-js))}

AntD官方例子

重置表单

使用resetFields方法

提交

通过调用validateFieldsAndScroll方法,会自动触发表单的校验规则,如果校验通过则匿名函数的第一个参数err会为空并且函数的第二个值values就是所有表单的值。

注意:获取的values数据是js类型的,所以需要js->clj转一下(js->clj values :keywordize-keys true)


数据转换问题

  • 最后顺便记录一下因为数据转换问题,在table页面踩过的一次坑:
(def columns [{:title "操作"
               :dataIndex "action"
               :render
               (fn [val row]
                 (prn "id---" row.craftwork_id) ;; 此时本地开发打印是有值的
                 (r/as-element
                  [:div
                   [:a {:href (str "/main/craftwork/form?id=" (:craftwork_id (js->clj row :keywordize-keys true)))} "编辑"]]))}])

在回调函数中拿到的row参数是个js类型的数据对象,我开发时使用以前惯用的js手法尝试了一下row.craftwork_id进行取值,发现没有问题,所以感觉不需要js->clj那么麻烦的转换取值,于是很多地方都这么直接js式的取值了,本地运行也完全没有问题。

虽然本地调试OK,但是,在项目发布到线上运行后却发现id拿不到了!其他地方的值也是一样。。。

  • js的多层嵌套对象数据一定不要直接使用“点”连续取值,一方面是刚才的问题,另一方面如果连续点多次,如果某次结果是undefined了,再继续点取值时就会崩溃了,所以写js时一定要小心规范。
  • clojure的get-in方法就比较友好了,无论对map数据取值多少层没值,都会安全的返回一个nil。
Tags: clojure Form clojurescript