移动端跨平台表单组件

227 阅读7分钟

背景

Taro 让跨端开发变得简单,一套代码,多端运行。但在实际开发中,表单处理却成了痛点:

  • 重复造轮子:每个表单都要手动封装组件,费时费力
  • 校验难维护:校验逻辑分散,容易遗漏,新增/删除字段可能引发问题
  • 代码冗余:复制粘贴导致代码臃肿,难以统一管理

为了解决这些问题,开发了一个表单组件 am-taro-form,让开发者只需关注业务,样式和校验交给组件间内部处理,让移动端表单开发变的简单。

组件简介

组件是基于Taro并使用React开发的,所以只能在Taro中使用。组件内置了Input、Select、Cascader、DatePicker组件,通过简单配置即可渲染不同的表单。可以在每个字段组件上配置校验,风格方式就是我们熟悉的 Antd Form表单校验,也提供了统一的错误样式;也可以根据项目需求扩展自己的表单项,让组件管理更简单;项目使用typescript的话,也会有友好的代码提示;

效果展示

基础用法

代码demo

import { View } from '@tarojs/components';
import { Form } from 'am-taro-form';
import { Button } from '@nutui/nutui-react-taro';

export default function Index() {
  const [form] = Form.useForm();

  return (
    <View className="index">
      <Form form={form} onFinish={console.log} onValuesChange={console.log}>
        <View>
          <Form.Item
            label="姓名"
            name="name"
            valueType="input"
            rules={[{ required: true, message: '请填写姓名' }]}
          />
          <Form.Item label="身高" name="height" valueType="input" suffix="cm" />
          <Form.Item label="年龄" name="age" valueType="input" />
        </View>
        <View
          style={{ display: 'flex', justifyContent: 'center', padding: 20 }}
        >
          <Button block onClick={() => form.submit()}>
            提交
          </Button>
        </View>
      </Form>
    </View>
  );
}

效果预览

image.png

自定义扩展表单类型

代码demo

/** 扩展组件定义 formItem.tsx */
import {
  createFormItem,
  ListItemStyle,
  ListItemStylePropsKeyList,
  selectPropsKeyList,
  FieldSelect,
} from 'am-taro-form';

import type {
  ListItemStyleProps,
  BaseSelectPickerProps,
  SelectOptionType,
  SelectPickerProps,
  FieldSelectProps,
} from 'am-taro-form';

interface TextProps extends ListItemStyleProps {
  /** 文本展示 */
  valueType: 'text';
  /** 文本值 */
  value?: string;
}

interface BaseGenderSelectProps extends ListItemStyleProps {
  fieldProps?: SelectPickerProps;
  placeholder?: string;
  /** 性别选择 */
  valueType: 'gender';
}

export type FieldGenderSelectProps = BaseGenderSelectProps &
  (
    | (Pick<
        BaseSelectPickerProps<SelectOptionType>,
        'onChange' | 'value' | 'title'
      > & {
        /** 设置value值类型, true 表示value 值为对象 */
        labelInValue: true;
      })
    | (Pick<
        BaseSelectPickerProps<string | number>,
        'onChange' | 'value' | 'title'
      > & {
        /** 设置value值类型, false 表示value 值为string或者number */
        labelInValue?: false;
      })
  );

/**
 * 布尔值选择
 * 扩展FieldSelect组件的类型简单写法,value值无法根据labelInValue值识别,可以参考性别组件类型定义,提示会更友好
 */
interface FieldBoolSelectProps extends Omit<FieldSelectProps, 'options'> {
  fieldProps?: SelectPickerProps;
  placeholder?: string;
  /** 布尔值选择 */
  valueType: 'bool';
}

const Text = (props: TextProps) => (
  <ListItemStyle {...props}>{props.value}</ListItemStyle>
);

const Gender = (props: FieldGenderSelectProps) => {
  return (
    <FieldSelect
      {...props}
      options={[
        { value: '1', label: '' },
        { value: '2', label: '' },
      ]}
    />
  );
};

const Boolean = (props: FieldBoolSelectProps) => {
  return (
    /* @ts-ignore */
    <FieldSelect
      {...props}
      options={[
        { value: 'Y', label: '' },
        { value: 'N', label: '' },
      ]}
    />
  );
};

const FormItem = createFormItem({
  text: [Text, [...ListItemStylePropsKeyList, 'value']],
  gender: [Gender, [...selectPropsKeyList.filter((key) => key !== 'options')]],
  bool: [Boolean, [...selectPropsKeyList.filter((key) => key !== 'options')]],
});

export default FormItem;

/** 扩展组件使用 */
import { View } from '@tarojs/components';
import { Form } from 'am-taro-form';
import { Button } from '@nutui/nutui-react-taro';
import FormItem from './formItem';

export default function Index() {
  const [form] = Form.useForm();

  return (
    <View>
      <Form form={form} onFinish={console.log} onValuesChange={console.log}>
        <View>
          <Form.Item
            label="姓名"
            name="name"
            valueType="input"
            rules={[{ required: true, message: '请填写姓名' }]}
          />
          <Form.Item label="身高" name="name1" valueType="input" suffix="cm" />
          <FormItem label="性别" name="gender" valueType="gender" labelInValue={false} />
          <FormItem label="是否汉族" name="isHan" valueType="bool" labelInValue={false} />
          <FormItem
            label="生日"
            name="birthday"
            valueType="date"
            startDate={new Date()}
          />
          <FormItem
            label="地区"
            name="area"
            valueType="select"
            labelInValue
            onChange={(v) => console.log(v)}
            options={[
              { value: 1, label: '南京市' },
              { value: 2, label: '无锡市' },
              { value: 3, label: '海北藏族自治区' },
              { value: 4, label: '北京市' },
              { value: 5, label: '连云港市' },
              { value: 8, label: '大庆市' },
              { value: 9, label: '绥化市' },
              { value: 10, label: '潍坊市' },
              { value: 12, label: '乌鲁木齐市' },
            ]}
          />
        </View>
        <View
          style={{ display: 'flex', justifyContent: 'center', padding: 20 }}
        >
          <Button block onClick={() => form.submit()}>
            提交
          </Button>
        </View>
      </Form>
    </View>
  );
}

效果预览

image.png

image.png

image.png

组件API

Form

属性名属性说明类型默认值
form通过Form.useForm() 创建FormInstanceForm.useForm()
initialValues表单默认值Object-
preserve表单删除时是否保留字段booleanfalse
validateMessages校验信息模板
onFieldsChange表单字段变更回调function-
onFinish当提交时所有校验通过的回调(values) => void-
onFinishFailed当提交时所有校验失败的回调({ values, errorFields, outOfDate }) => void-
onValuesChange表单值改变的回调(changedValues, values) => void-

FormItem 通用属性

属性名属性说明类型默认值
dependencies设置依赖字段NamePath[]-
getValueFromEvent设置如何将 event 的值转换成字段值(...args: EventArgs) => StoreValue-
name字段名,支持数组NamePath-
label左边标题文本ReactNode-
trigger设置收集字段值变更的时机。stringonChange
validateTrigger设置字段校验的时机string | string[] | false onChange
rules校验规则,设置字段的校验逻辑Rule[]-
preserve当字段被删除时保留字段值booleantrue
initialValue设置子元素默认值,如果与 Form 的 initialValues 冲突则以 Form 为准-
messageVariables默认验证字段的信息Record<string, string>-
getValueProps为子元素添加额外的属性 (不建议通过 getValueProps 生成动态函数 prop,请直接将其传递给子组件)-
valuePropName子节点的值的属性。注意:Switch、Checkbox 的 valuePropName 应该是 checked,否则无法获取这个两个组件的值。该属性为 getValueProps 的封装,自定义 getValueProps 后会失效string-
validateFirst当某一规则校验不通过时,是否停止剩下的规则的校验。设置 parallel 时会并行校验boolean | parallel-
validateDebounce设置防抖,延迟毫秒数后进行校验number-
required是否显示必填表示(*)boolean-
onClick当前行点击回调-
clickable是否显示点击反馈样式boolean-
showArrowRight是否展示右侧箭头,如果没有配置onClick是函数的时候展示boolean-
help帮助文案,如果有错误文案将不展示ReactNode-
errorText错误文案,默认红色字体ReactNode-
className组件classstring-
divider是否展示底部分割线booleantrue
suffix末尾额外的文案,在右侧箭头之前ReactNode-
fieldProps透传给子组件额外的属性,每个自定义表单项值是不同的React<string, any>-
valueType表单项类型'input' | 'date' | 'select' | 'cascader'input
mode表单模式,edit:编辑模式,readOnly:只读模式'edit' | 'readOnly'-

FormItem - input

属性名属性说明类型默认值
disabled是否禁用boolean-
value表单值string-
placeholder提示文本string-
onBlur失去焦点回调-
onFocus得到焦点时的回调-

FormItem - date

属性名属性说明类型默认值
startDate选择器开始时间Date十年前
endDate选择器结束时间Date十年后
type日期类型'date' | 'time' | 'year-month' | 'month-day' | 'datehour' | 'datetime' | 'hour-minutes'date
title选择器标题string-

FormItem - select

属性名属性说明类型默认值
title选择框标题string
options选项列表{value: stringnumber;label: stringnumber;}[]
onChange选择项变更的回调
value选项项值stringobject
labelInValue设置value值类型, true 表示value 值为对象booleanfalse

FormItem - cascader

属性名属性说明类型默认值
options选项列表
onChange选择变更回调
labelInValue值类型boolean
valueToList值是否转成list,false只返回最后一项boolean
fieldProps透传给ca's

Form.List

为字段提供数组化管理。

属性名属性说明类型默认值
children渲染函数(fields: Field[], operation: { add, remove, move }, meta: { errors }) => React.ReactNode
initialValue设置子元素默认值,如果与 Form 的 initialValues 冲突则以 Form 为准any[]
name字段名,支持数组。List 本身也是字段,因而 getFieldsValue() 默认会返回 List 下所有值,你可以通过参数改变这一行为NamePath

Rule

Rule 支持接收 object 进行配置,也支持 function 来动态获取 form 的数据:

属性名类型
enumany[]
lennumber
maxnumber
messagestring
minnumber
patternRegExp
requiredboolean
transform(value) => any
typestring
validator(rule, value, callback: (error?: string) => void, form) => Promisevoid
whitespaceboolean
validateTriggerstringstring[]

预使用


# 克隆项目代码
git clone https://github.com/yigexiaoairen/am-taro-form.git
# 进入项目目录
cd am-taro-form
# 切换项目主分支
git checkout main
# 安装项目依赖
yarn

# 启动项目
yarn dev:h5

结语

  1. 此组件库正常Taro支持的平台都能支持,由于个人精力有限和不同平台要求不同等原因仅调试了H5和微信小程序平台,微信小程序使用需要使用自己的开发者账号其他平台亦如此;
  2. 组件主要是基于rc-field-form组件进行开发的,更多用法和细节可以参考这个组件的用法;
  3. 项目现在还处于初版,缺少实践,功能可能没那么完善,欢迎使用和提缺陷,有什么问题或者需要也可以发出来一起讨论;
  4. 如果有typescript相关的问题也可以发出来一起讨论;