• Vant React

Form 表单

介绍

用于数据录入、校验,支持输入框、单选框、复选框、文件上传等类型。

引入

import { Form } from "@taroify/core"

代码演示

基础用法

在表单中,每个 Form.Item 代表一个表单项

function BasicForm() {
  const onSubmit = (event: BaseEventOrig<FormProps.onSubmitEventDetail>) => {
    Toast.open(JSON.stringify(event.detail.value))
  }

  return (
    <Form onSubmit={onSubmit}>
      <Toast id="toast" />
      <Cell.Group inset>
        <Form.Item name="username" rules={[{ required: true, message: "请填写用户名" }]}>
          <Form.Label>用户名</Form.Label>
          <Form.Control>
            <Input placeholder="用户名" />
          </Form.Control>
        </Form.Item>
        <Form.Item name="password" rules={[{ required: true, message: "请填写密码" }]}>
          <Form.Label>密码</Form.Label>
          <Form.Control>
            <Input password placeholder="密码" />
          </Form.Control>
        </Form.Item>
        <Field
          name="text"
          label={{ align: "left", children: "文本" }}
          rules={[{ required: true, message: "请填写文本" }]}
        >
          <Input placeholder="请输入文本" />
        </Field>
      </Cell.Group>
      <View style={{ margin: "16px" }}>
        <Button shape="round" block color="primary" formType="submit">
          提交
        </Button>
      </View>
    </Form>
  )
}

使用Field组件

function BasicForm() {
  const onSubmit = (event: BaseEventOrig<FormProps.onSubmitEventDetail>) => {
    Toast.open(JSON.stringify(event.detail.value))
  }

  return (
    <Form onSubmit={onSubmit}>
      <Toast id="toast" />
      <Cell.Group inset>
        <Field  label="用户名" name="username" rules={[{ required: true, message: "请填写用户名" }]}>
          <Input placeholder="用户名" />
        </Field>
        <Field label="密码" name="password" rules={[{ required: true, message: "请填写密码" }]}>
          <Input password placeholder="密码" />
        </Field>
      </Cell.Group>
      <View style={{ margin: "16px" }}>
        <Button shape="round" block color="primary" formType="submit">
          提交
        </Button>
      </View>
    </Form>
  )
}

校验规则

通过 rules 定义表单校验规则,所有可用字段见下方表格

function FormWithRules() {
  const asyncValidator = (val: any) =>
    new Promise<boolean>((resolve) => {
      Toast.loading("验证中...")

      setTimeout(() => {
        Toast.close("toast")
        resolve(/\d{6}/.test(val))
      }, 1000)
    })

  function onValidate(errors: FormValidError[]) {
    Toast.open({
      style: {
        textAlign: "left",
      },
      message: JSON.stringify(errors, undefined, 2),
    })
  }

  return (
    <Form defaultValues={{ validatorMessage: "abc" }} onValidate={onValidate}>
      <Toast id="toast" />
      <Cell.Group inset>
        <Field label="文本" name="pattern" rules={[{ pattern: /\d{6}/, message: "请输入正确内容" }]}>
          <Input placeholder="正则校验" />
        </Field>
        <Field
          label="文本"
          name="validator"
          rules={[{ validator: (val) => /1\d{10}/.test(val), message: "请输入正确内容" }]}
        >
          <Input placeholder="函数校验" />
        </Field>
        <Field
          label="文本"
          name="validatorMessage"
          rules={[{ validator: (val) => `${val ?? ""} 不合法,请重新输入` }]}
        >
          <Input placeholder="校验函数返回错误提示" />
        </Field>
        <Field
          label="文本"
          name="asyncValidator"
          rules={[{ validator: asyncValidator, message: "请输入正确内容" }]}
        >
          <Input placeholder="异步函数校验" />
        </Field>
      </Cell.Group>
      <View style={{ margin: "16px" }}>
        <Button shape="round" block color="primary" formType="submit">
          提交
        </Button>
      </View>
    </Form>
  )
}

禁用表单

设置 disabled 后,会为 Form 内部的 taroify 组件 Input, Textarea, Checkbox, Switch, Checkbox.Group, Radio.Group, Rate, Slider, Stepper, Uploader 设置 disabled

form设置disabled后,也可以单独为表单项和组件设置disabled={false}, 优先级:表单 < 表单项 < 组件

表单项类型 - 开关

在表单中使用 Switch 组件

<Field label="开关" name="switch">
  <Switch size={20} />
</Field>

表单项类型 - 复选框

在表单中使用 Checkbox 组件

<Field label="复选框" name="checkbox">
  <Checkbox shape="square" />
</Field>
<Field label="复选框组" name="checkboxGroup">
  <Checkbox.Group direction="horizontal">
    <Checkbox name="1" shape="square">
      复选框 1
    </Checkbox>
    <Checkbox name="2" shape="square">
      复选框 2
    </Checkbox>
  </Checkbox.Group>
</Field>

表单项类型 - 单选框

在表单中使用 Radio 组件

<Field label="单选框" name="radio">
  <Radio.Group direction="horizontal">
    <Radio name="1">单选框 1</Radio>
    <Radio name="2">单选框 2</Radio>
  </Radio.Group>
</Field>

表单项类型 - 步进器

在表单中使用 Stepper 组件

<Field label="步进器" name="stepper">
  <Stepper />
</Field>

表单项类型 - 评分

在表单中使用 Rate 组件

<Field label="评分" name="rate">
  <Rate />
</Field>

表单项类型 - 滑块

在表单中使用 Slider 组件

<Field label="滑块" name="slider">
  <Slider />
</Field>

表单项类型 - 文件上传

在表单中使用 Uploader 组件

function UploaderField() {
  const itemRef = useRef<FormItemInstance>()

  function onUpload() {
    chooseImage({
      count: 1,
      sizeType: ["original", "compressed"],
      sourceType: ["album", "camera"],
    }).then(({ tempFiles }) => {
      itemRef.current?.setValue([
        ...(itemRef.current?.getValue() ? [...itemRef.current?.getValue()] : []),
        {
          url: tempFiles[0].path,
          type: tempFiles[0].type,
          name: tempFiles[0].originalFileObj?.name,
        },
      ])
    })
  }

  return (
    <Field label="文件上传" ref={itemRef} name="uploader">
      <Uploader multiple maxFiles={2} onUpload={onUpload} />
    </Field>
  )
}

表单项类型 - 选择器

在表单中使用 Picker 组件

function PickerField() {
  const itemRef = useRef<FormItemInstance>()
  const [open, setOpen] = useState(false)
  const columns = useMemo(() => [
    { label: "杭州", value: "Hangzhou" },
    { label: "宁波", value: "Ningbo" },
    { label: "温州", value: "Wenzhou" },
    { label: "绍兴", value: "Shaoxing" },
    { label: "湖州", value: "Huzhou" }
  ], [])


  return (
    <>
      <Field label="选择器" ref={itemRef} name="picker" clickable isLink>
        <Input readonly placeholder="点击选择城市" onClick={() => setOpen(true)} />
      </Field>
      <Popup open={open} rounded placement="bottom" onClose={setOpen}>
        <Picker
          title="选择城市"
          columns={columns}
          onCancel={() => setOpen(false)}
          onConfirm={(newValue) => {
            itemRef.current?.setValue(newValue)
            setOpen(false)
          }}
        >
        </Picker>
      </Popup>
    </>
  )
}

表单项类型 - 时间选择器

在表单中使用 DatetimePicker 组件

function DatetimePickerField() {
  const itemRef = useRef<FormItemInstance>()
  const [open, setOpen] = useState(false)

  const formatDate = (date: any) => {
    if (date) {
      const hours = _.padStart(_.toString(date?.getHours()), 2, "0")
      const minutes = _.padStart(_.toString(date?.getMinutes()), 2, "0")
      return `${hours}:${minutes}`
    }
  }

  return (
    <>
      <Field label="时间选择" ref={itemRef} name="datetimePicker" clickable isLink>
        {(controller) => (
          <Input
            value={formatDate(controller.value)}
            disabled={controller.disabled}
            readonly
            placeholder="点击选择时间"
            onClick={() => setOpen(true)}
          />
        )}
      </Field>
      <Popup open={open} rounded placement="bottom" onClose={setOpen}>
        <DatetimePicker
          type="hour-minute"
          onCancel={() => setOpen(false)}
          onConfirm={(newValue) => {
            itemRef.current?.setValue(newValue)
            setOpen(false)
          }}
        >
        </DatetimePicker>
      </Popup>
    </>
  )
}

表单项类型 - 日历

在表单中使用 Calendar 组件

function CalendarField() {
  const itemRef = useRef<FormItemInstance>()
  const [open, setOpen] = useState(false)

  const formatDate = (date: any) => {
    if (date) {
      const months = _.padStart(_.toString(date?.getMonth() + 1), 2, "0")
      const days = _.padStart(_.toString(date?.getDate()), 2, "0")
      return `${months}-${days}`
    }
  }

  return (
    <>
      <Field label="日历" ref={itemRef} name="calendar" clickable isLink>
        {(controller) => (
          <Input
            value={formatDate(controller.value)}
            disabled={controller.disabled}
            readonly
            placeholder="点击选择日期"
            onClick={() => setOpen(true)}
          />
        )}
      </Field>
      <Calendar
        type="single"
        poppable
        showPopup={open}
        onConfirm={(newValue) => {
          itemRef.current?.setValue(newValue)
          setOpen(false)
        }}
      >
      </Calendar>
    </>
  )
}

动态增减表单项

通过Form.List动态增减表单项

function DynamicForm() {
  const ref = useRef<FormInstance>(null)
  const onManual = () => {
    ref.current?.setValues({
      contacts: [
        { name: "小红", phone: "10000" },
        { name: "小绿", phone: "10086" }
      ]
    })
  }
  return <Form ref={ref}  onSubmit={(e) => {
    Toast.open(JSON.stringify(e.detail.value, undefined, 2))
  }}
  >
    <Form.List name="contacts" defaultValue={[{name: "小明", phone: "10086"}]}>
      {
        (fields, { add, remove }) => <Cell.Group inset>
          {
            fields.map((field, idx) => <Fragment key={field.key}>
              <Field label="姓名" name={`${field.name}.name`} key={`${field.key}.name`}>
                {
                  ({ value, onChange }) => <>
                    <Input placeholder="请输入姓名" value={value} onChange={(e) => { onChange?.(e.detail.value) }} />
                    <View style={{ flexShrink: 0 }} onClick={() => { remove(idx) }}>删除</View>
                  </>
                }
              </Field>
              <Field label="电话" name={`${field.name}.phone`} key={`${field.key}.phone`} rules={[{ required: true, message: "必填" }]}>
                <Input placeholder="请输入电话" />
              </Field>
            </Fragment>)
          }
          <Button variant="text" color="primary" onClick={() => add({})} >添加联系人</Button>
        </Cell.Group>
      }
    </Form.List>
    <View style={{ margin: "16px" }}>
      <Button shape="round" block color="primary" formType="submit">
        提交
      </Button>
      <Button style={{ marginTop: "16px" }} shape="round" block color="info" onClick={onManual}>
        手动
      </Button>
      <Button style={{ marginTop: "16px" }} shape="round" block color="warning" formType="reset">
        重置
      </Button>
    </View>
  </Form>
}

依赖与自动更新

通过dependencies自动触发校验

function DependenciesDemo() {
  const formRef = useRef<FormInstance>()
  const onSubmit = (event: BaseEventOrig<FormProps.onSubmitEventDetail>) => {
    Toast.open(JSON.stringify(event.detail.value))
  }

  return (
    <Form ref={formRef} onSubmit={onSubmit} validateTrigger="onChange">
      <Toast id="toast" />
      <Cell.Group inset>
        <Form.Item name="username" rules={[{ required: true, message: "请输入用户名" }]}>
          <Form.Label>用户名</Form.Label>
          <Form.Control>
            <Input placeholder="用户名" />
          </Form.Control>
        </Form.Item>
        <Form.Item name="password" rules={[{ required: true, message: "请输入密码" }]}>
          <Form.Label>密码</Form.Label>
          <Form.Control>
            <Input placeholder="密码" />
          </Form.Control>
        </Form.Item>
        <Form.Item
          name="confirmPassword"
          dependencies={["password"]}
          rules={[{ required: true, message: "请再次输入密码" }, {
            validator: (val) => {
              return formRef.current?.getValues<any>()?.password === val ? true : "密码不一致"
            }
          }]}
        >
          <Form.Label>确认密码</Form.Label>
          <Form.Control>
            <Input placeholder="确认密码" />
          </Form.Control>
        </Form.Item>
      </Cell.Group>
      <View style={{ margin: "16px" }}>
        <Button shape="round" block color="primary" formType="submit">
          提交
        </Button>
      </View>
    </Form>
  )
}

通过shouldUpdate和noStyle触发重新渲染, Form.Item里包裹的子组件必须由函数返回,否则 shouldUpdate 不会起作用, Field暂不支持

function ShouldUpdateDemo() {
  const formRef = useRef<FormInstance>()
  return <Form
    ref={formRef}
    onSubmit={(e) => {
      Toast.open(JSON.stringify(e.detail.value, undefined, 2))
    }}
  >
    <Toast id="toast" />
    <Cell.Group inset>
      <Form.Item name="type">
        <Form.Label>复选框</Form.Label>
        <Form.Control>
          <Checkbox shape="square" />
        </Form.Control>
      </Form.Item>
      <Form.Item name="radio">
        <Form.Label>单选框</Form.Label>
        <Form.Control>
          <Radio.Group direction="horizontal">
            <Radio name="1">单选框 1</Radio>
            <Radio name="2">单选框 2</Radio>
          </Radio.Group>
        </Form.Control>
      </Form.Item>
      <Form.Item noStyle shouldUpdate={(prev, cur) => prev.type !== cur.type}>
        {
          () => {
            const type = formRef.current?.getValues<any>()?.type
            if (type) {
              return <Form.Item name="a1">
                <Form.Label>复选框选中</Form.Label>
                <Form.Control><Input /></Form.Control>
              </Form.Item>
            } else {
              return <>
                <Form.Item name="a2">
                  <Form.Label>复选框未选中</Form.Label>
                  <Form.Control><Input /></Form.Control>
                </Form.Item>
                <Form.Item name="a3">
                  <Form.Label>复选框未选中</Form.Label>
                  <Form.Control><Input /></Form.Control>
                </Form.Item>
              </>
            }
          }
        }
      </Form.Item>
      <Form.Item noStyle shouldUpdate={(prev, cur) => prev.radio !== cur.radio}>
        {
          () =>  <Field name="cc" label={`单选框${formRef.current?.getValues<any>()?.radio}`}>
            <Input placeholder={`结果${JSON.stringify(formRef.current?.getValues<any>())}`} />
          </Field>
        }
      </Form.Item>
    </Cell.Group>
    <View style={{ margin: "16px" }}>
      <Button shape="round" block color="primary" formType="submit">
        提交
      </Button>
    </View>
  </Form>
}

API

Form Props

参数说明类型默认值
defaultValues表单默认值object
labelAlign表单项 label 对齐方式,可选值为 center rightstringleft
controlAlign表单项 control 对齐方式,可选值为 center rightstringleft
validateTrigger表单校验触发时机,可选值为 onChangeonSubmit,详见下表stringonBlur
colon是否在 label 后面添加冒号booleanfalse
disabled是否禁用表单booleanfalse

Form Events

事件名说明回调参数
onSubmit提交表单且验证通过后触发event: BaseEventOrig<FormProps.onSubmitEventDetail>
onReset重置表单后触发event: BaseEventOrig
onValidate提交表单且验证不通过后触发errors: { name: string, errors: string[] }[]
onValuesChange字段值更新后触发changedValues: object, allValues: object

Form Methods

通过 ref 可以获取到 Form 实例并调用实例方法。

方法名说明参数返回值
setValues设置表单值(浅合并)object-
getValues获得表单值,支持传入 name 来获得单个或部分表单项name?: string | string[]object
setErrors设置表单验证错误信息(浅合并)FormValidError[]void
getErrors获得表单验证错误信息,支持传入 name 来获得单个或部分表单项name?: string | string[]FormValidError[]
validate验证表单,支持传入 name 来验证单个或部分表单项name?: string | string[]Promise
submit提交表单,与点击提交按钮的效果等价--
reset重置表单-void

validateTrigger 可选值

通过 validateTrigger 属性可以自定义表单校验的触发时机。

描述
onSubmit仅在提交表单时触发校验
onBlur在提交表单和输入框失焦时触发校验
onChange在提交表单和输入框内容变化时触发校验

Form.Item Props

参数说明类型默认值
name表单项名称,提交表单的标识符string-
defaultValue表单项默认值any-
required是否显示表单必填星号booleanfalse
rules表单校验规则FormRule[]-
dependencies当依赖的字段值改变时,触发自身的校验string[]-
shouldUpdate当值为true时,触发当前区域重新渲染boolean|(prevValues, curValues) => boolean-
noStyle直接渲染childrenboolean-

属性继承自 Cell 组件,更多属性参见:Cell 组件

Rule 数据结构

使用 Form.Item 的 rules 属性可以定义校验规则,可选属性如下:

键名说明类型
required是否为必选字段,当值为空字符串、空数组、undefinednull 时,校验不通过boolean
message错误提示文案string | (value, rule) => string
validator通过函数进行校验(value, rule) => boolean | string | Promise
pattern通过正则表达式进行校验RegExp
trigger本项规则的触发时机,可选值为 onChangeonBlurstring
formatter格式化函数,将表单项的值转换后进行校验(value, rule) => any

Form.Label Props

参数说明类型默认值
align对齐方式,可选值为 center rightstringleft
colon是否在 label 后面添加冒号booleanfalse

Form.Feedback Props

参数说明类型默认值
align对齐方式,可选值为 center rightstringleft
status反馈状态,可选值为 valid warning invalidstring-

Form.Control Props

参数说明类型默认值
align对齐方式,可选值为 center rightstringleft
children内容ReactNode|((controller: FormController) => ReactNode)
interface FormController<V> {
  value?: V // 当前表单项值
  validateStatus?: FormValidateStatus // 当前表单项验证状态
  disabled?: boolean // 当前表单项是否禁用

  onChange?(value: V): void // 改变当前表单项值

  onBlur?(value: V): void // 触发onBlur当前表单项校验
}

Tips: 使用onBlur触发表单验证,自定义表单项时,要手动调用onBlur,才能触发校验

Form.List

参数说明类型
children渲染函数(fields: { key: string, name: string }[], operation: { add, remove }) => ReactNode
name表单项名称,提交表单的标识符string
defaultValue表单项默认值any

类型定义

通过@taroify/core/form导入组件类型:

import { 
  FormRule, FormItemInstance, FormListInstance, FormInstance 
} from "@taroify/core/form"

主题定制

样式变量

组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 ConfigProvider 组件。

名称默认值描述
form-label-width6.2em-
form-label-colorvar(—gray-7)-
form-label-margin-rightvar(—padding-sm)-
form-label-disabled-colorvar(—gray-5)-
form-item-icon-size16px * $hd-
form-item-right-icon-colorvar(—gray-6)-
form-item-right-icon-padding0 var(—padding-xs)-
form-item-right-icon-margin-right-8px * $hd-
form-item-right-button-margin-leftvar(—padding-xs)-
form-control-colorvar(—text-color)-
form-control-min-heightvar(—cell-line-height)-
form-feedback-font-sizevar(—font-size-sm)-
form-feedback-valid-colorvar(—success-color)-
form-feedback-warning-colorvar(—warning-color)-
form-feedback-invalid-colorvar(—danger-color)-