Skip to content

Runc2333/taro-form-react

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

64 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Taro 3/4 表单组件

适用于 Taro 3.x/4.x 的简单表单组件,不包含任何输入组件,只提供简单的表单布局/数据收集/联动/校验功能。仅支持 React。

特点

  • 完全响应式。你说用户体验?用户体验有我开发体验重要吗。
  • 强劲的动态表单支持,支持动态增删 Form.Item,暴打 @antmjs/vantui 的表单组件。
  • 表单快照,随时 capture / restore,缓存用户编辑状态不求人。
  • 性能跟屎一样,但是我肯定不会爆炸。

安装

yarn add taro-form-react

使用

基础用法

import Form from "taro-form-react";
// 样式只需要在入口文件引入一次
// 包含 Label 和一些简单布局样式
import "taro-form-react/dist/styles/index.scss";
// 或者使用 css
import "taro-form-react/dist/styles/index.css";

import { Input, Button } from "@tarojs/components";

import type { FormActions } from "taro-form-react";

const Component = () => {
  const formRef = useRef<FormActions>();

  const handleSubmit = () => {
    formRef.current?.submit();
  }

  const handleReset = () => {
    formRef.current?.reset();
  }

  const handleFinish = (values) => {
    console.log(values);
  }

  const handleFinishedFailed = (errors) => {
    console.log(errors);
  }

  return (
    <Form
      // ref 用于触发表单校验、设置值、获取值等操作
      // 具体支持的操作可参考后续文档
      ref={formRef}
      // 丢弃值为 undefined 和 null 的字段
      // 默认为 true,如需保留请设置为 false
      omitNil={true}
      // 提交成功时触发
      onFinish={handleFinish}
      // 提交失败时触发(有表单元素校验失败)
      onFinishFailed={handleFinishedFailed}
    >
      {/* 表单元素示例 */}

      {/* 基础控件 */}
      <Form.Item 
        // Form.Item 必须存在 name 属性
        // 仅支持数组形式,如 ["field", "array", 0, "name"]
        name={["name"]}
        // Form.Item 应该劫持的输入事件,默认为 onChange
        trigger="onInput"
        // Form.Item 应该劫持的 value,默认为 value
        valuePropName="value"
        // 从事件中获取值的方法,默认为 (...args) => args[0]
        getValueFromEvent={e => e.detail.value}
      >
        {/* 必须存在单个子元素,不能是 React.Fragment */}
        {/* Form.Item 会劫持该元素的输入事件 */}
        <Input placeholder="请输入你的姓名" />
      </Form.Item>

      {/* 保留控件 */}
      {/* Form 不会保留不存在对应控件的值 */}
      {/* 即使你调用了 setFieldValue 设置了它 */}
      {/* 如果需要保留某些值,但不需要展示给用户编辑 */}
      {/* 可以使用 Form.Keep */}
      <Form.Keep
        fields={[
          ["field", "array", 0, "gender"],
          ["field", "array", 1, "gender"],
        ]}
      />

      {/* 上下文控件 */}
      {/* 用于实现复杂的表单联动 */}
      {/* Form.Provider 只暴露只读方法(data / getFieldValue 等) */}
      {/* 如果需要写入表单数据,请通过 formRef 调用 */}
      <Form.Provider>
        {({ data }) => {
          return (
            <>
              <Text>你好,{data.name}</Text>
              <Button
                onClick={() => {
                  formRef.current?.setFieldValue(["name"], "");
                }}
              >
                清空姓名
              </Button>
            </>
          );
        }}
      </Form.Provider>
      <Button onClick={handleSubmit}>提交</Button>
      <Button onClick={handleReset}>重置</Button>
    </Form>
  )
}

校验

<Form.Item
  name={["phone"]}
  label="手机号"
  // 设置 required 会自动添加必填校验规则
  // 错误文案默认为 "{label} is required"
  // 可通过 Form 的 getRequiredMessage 自定义
  required
  rules={[
    // 正则校验,值非空时才触发
    { pattern: /^1\d{10}$/, message: "请输入正确的手机号" },
  ]}
  // 在哪些事件上触发校验,默认跟随 trigger
  validateTrigger={["onInput", "onBlur"]}
  trigger="onInput"
  getValueFromEvent={e => e.detail.value}
>
  <Input placeholder="请输入手机号" />
</Form.Item>

<Form.Item
  name={["password"]}
  label="密码"
  rules={[
    // 长度校验
    { min: 6, max: 20, message: "密码长度 6-20 位" },
    // 自定义异步校验
    {
      validator: async (value) => {
        if (value?.includes(" ")) {
          return "密码不能包含空格";
        }
      },
    },
  ]}
  trigger="onInput"
  getValueFromEvent={e => e.detail.value}
>
  <Input password placeholder="请输入密码" />
</Form.Item>

字段联动

<Form
  initialValues={{ type: "personal" }}
>
  <Form.Item
    name={["type"]}
    label="类型"
    trigger="onChange"
  >
    <Radio.Group>
      <Radio value="personal">个人</Radio>
      <Radio value="company">企业</Radio>
    </Radio.Group>
  </Form.Item>

  {/* 通过 Form.Provider 实现条件渲染 */}
  {/* data 是响应式的,type 变化时会自动重渲染 */}
  <Form.Provider>
    {({ data }) => {
      if (data.type === "company") {
        return (
          <Form.Item
            name={["companyName"]}
            label="企业名称"
            required
            trigger="onInput"
            getValueFromEvent={e => e.detail.value}
          >
            <Input placeholder="请输入企业名称" />
          </Form.Item>
        );
      }
      return null;
    }}
  </Form.Provider>

  {/* 使用 dependencies 实现依赖校验 */}
  {/* 当 password 变化时,confirmPassword 会自动重新校验 */}
  <Form.Item
    name={["confirmPassword"]}
    label="确认密码"
    dependencies={[["password"]]}
    rules={[{
      validator: async (value) => {
        const password = formRef.current?.getFieldValue(["password"]);
        if (value && value !== password) {
          return "两次输入的密码不一致";
        }
      },
    }]}
    trigger="onInput"
    getValueFromEvent={e => e.detail.value}
  >
    <Input password placeholder="请再次输入密码" />
  </Form.Item>
</Form>

字段同步

<Form>
  {/* 单源同步:province 变化时,自动将值同步到 city 和 district */}
  <Form.Sync source={["province"]} target={[["city"], ["district"]]} />

  {/* 多字段互相同步:任一字段变化时,其值同步到其余字段 */}
  <Form.Sync fields={[["startDate"], ["endDate"]]} />
</Form>

值转换

<Form.Item
  name={["date"]}
  label="日期"
  // transform 在 getFieldsFormattedValue / submit 时生效
  // 可以将组件值转换为需要的格式
  transform={(value) => value?.format("YYYY-MM-DD")}
>
  <DatePicker />
</Form.Item>

<Form.Item
  name={["address"]}
  label="地址"
  // 当 transformBehavior 为 "merge" 时
  // transform 返回的对象会被 merge 到整个表单数据中
  // 此时 name 会被忽略
  transform={(value) => ({
    province: value?.[0],
    city: value?.[1],
    district: value?.[2],
  })}
>
  <RegionPicker />
</Form.Item>

快照与恢复

import type { FormActions, FormSnapshot } from "taro-form-react";

const EditPage = () => {
  const formRef = useRef<FormActions>();

  // 页面进入时恢复草稿
  useEffect(() => {
    const saved = localStorage.getItem("draft");
    if (saved) {
      formRef.current?.restore(JSON.parse(saved));
    }
  }, []);

  // 页面离开时保存草稿
  const handleSaveDraft = () => {
    const snapshot = formRef.current?.capture();
    if (snapshot) {
      localStorage.setItem("draft", JSON.stringify(snapshot));
    }
  };

  return (
    <Form ref={formRef} onFinish={handleSubmit}>
      {/* ... */}
      <Button onClick={handleSaveDraft}>保存草稿</Button>
    </Form>
  );
};

在子组件中访问表单

import { useFormContext } from "taro-form-react";

// 在 Form 内部的任意子组件中使用
const SubmitButton = () => {
  // useFormContext 返回完整的表单上下文
  // 包含所有数据和操作方法
  const { data, validateFields, isFieldsTouched } = useFormContext();

  const disabled = !isFieldsTouched();

  return (
    <Button
      disabled={disabled}
      onClick={async () => {
        const errors = await validateFields();
        if (!errors) {
          console.log("提交数据:", data);
        }
      }}
    >
      提交
    </Button>
  );
};

API

NamePath = (string | number)[],所有字段名均使用数组形式,如 ["user", "address", 0, "city"]

Form Props

  • initialValues?: Record<string, any> — 表单初始值
  • omitNil?: boolean(默认 true)— 提交时是否丢弃值为 undefined / null 的字段
  • onFinish?: (values: Record<string, any>) => void — 提交成功(校验通过)回调
  • onFinishFailed?: (errors: { name: NamePath; errors: string[] }[]) => void — 提交失败(校验未通过)回调
  • onFieldsChange?: (changedFields, allFields) => void — 字段注册/卸载时触发
  • onValuesChange?: (changedValues, allValues) => void — 字段值变更时触发

全局配置项

以下配置作用于所有子 Form.Item,也可在每个 Form.Item 上单独覆盖:

  • colon?: boolean — Label 是否显示冒号
  • labelProps?: FormLabelProps — 传递给 Label 的 props
  • layout?: "horizontal" | "vertical" — 布局方向
  • validateFirst?: boolean | "parallel"(默认 false)— true 遇到第一个校验错误即停止;"parallel" 并行执行所有规则
  • showErrors?: boolean — 是否在 Form.Item 下方显示错误文本
  • passthroughErrors?: boolean — 是否将 hasErrorerrors 透传给子组件 props
  • transformBehavior?: "merge" | "replace" — 当 Form.Item 设置了 transform 时,"merge" 将转换结果合并到表单数据,"replace" 直接替换对应字段
  • updateTickLimit?: number(默认 50)— onChange 节流间隔(ms),过小可能导致数据异常
  • getRequiredMessage?: (label: string) => string — 自定义 required 规则的默认错误文案

Form.Item Props

  • name: NamePath必填,字段路径
  • label?: ReactNode(默认 null)— 标签文本,传 null 不渲染标签
  • required?: boolean — 是否必填,会自动添加 required 规则(如果 rules 中没有)
  • rules?: Rule[] — 校验规则,见下方「校验规则」
  • trigger?: string(默认 "onChange")— 劫持子组件的哪个事件来收集值
  • valuePropName?: string(默认 "value")— 劫持子组件的哪个 prop 来注入值
  • getValueFromEvent?: (...args) => any(默认 (...args) => args[0])— 从事件参数中提取值
  • initialValue?: any — 字段初始值(优先级高于 Form 的 initialValues
  • dependencies?: NamePath[] — 依赖字段,依赖变化时自动重新校验
  • noStyle?: boolean — 不渲染外层容器,直接返回子组件
  • hidden?: boolean — 隐藏模式,不渲染子组件但保留字段注册(Form.Keep 内部使用)
  • validateTrigger?: string[](默认 [trigger])— 触发校验的事件
  • transform?: (value: any) => any — 提交时对值进行转换
  • className?: classNames.Argument — 外层容器 className
  • innerClassName?: classNames.Argument — 内层容器 className

Form.Item 要求有且仅有一个子元素(不能是 Fragment),会劫持该元素的输入事件和 value prop。

Form.Keep

保留不需要展示给用户的字段值。Form 默认在字段对应的 Form.Item 卸载后会清除该字段的数据,如果需要保留某些字段的值,使用 Form.Keep。

  • fields: NamePath[] — 需要保留的字段列表
<Form.Keep fields={[["hiddenField"], ["nested", "value"]]} />

Form.Sync

字段同步组件,当源字段值变化时自动同步到目标字段。两种用法:

单源多目标

<Form.Sync source={["province"]} target={[["city"], ["district"]]} />

source 变化时,将其值同步到所有 target 字段。

多字段互相同步

<Form.Sync fields={[["fieldA"], ["fieldB"], ["fieldC"]]} />

fields 中任一字段变化时,其值同步到其余所有字段。

Form.Label

Label 组件,通常由 Form.Item 自动渲染,也可独立使用。

  • label?: ReactNode — 标签文本
  • required?: boolean — 是否显示必填星号
  • size?: "normal" | "small"(默认 "normal"
  • colon?: boolean(默认 true)— 是否显示冒号
  • addonAfter?: ReactNode — 标签后附加内容
  • asteriskTookSpace?: boolean(默认 true)— 星号是否占据空间(不必填时保留占位)
  • className?: classNames.Argument
  • labelClassName?: classNames.Argument
  • colonClassName?: classNames.Argument
  • containerClassName?: classNames.Argument

Form.Provider

Render Props 模式的上下文访问组件,用于实现复杂的表单联动。

<Form.Provider>
  {({ data, getFieldValue, getFieldsValue, getFields, getFieldError, getFieldsFormattedValue, validateFields, isFieldsTouched, capture, restore, submit, reset }) => {
    return <Text>{data.name}</Text>;
  }}
</Form.Provider>

回调参数是只读的表单上下文(不包含 setFieldValuesetFieldsresetFieldssetFieldErrorsetData 等写入方法),加上 submitreset。如果需要在 Provider 内写入表单数据,请通过 formRef 调用对应方法。

FormActions(ref 方法)

通过 ref 获取表单实例,调用以下方法:

const formRef = useRef<FormActions>();
// ...
<Form ref={formRef}>

数据操作

  • setData(data: Record<string, any>) — 直接替换整个表单数据
  • setFieldValue(name: NamePath, value: any) — 设置单个字段的值
  • getFieldValue(name: NamePath): any — 获取单个字段的值
  • getFieldsValue(nameList?: NamePath[]): Record<string, any> — 获取多个字段的值,不传参返回所有
  • getFieldsFormattedValue(nameList?: NamePath[]): Promise<Record<string, any>> — 获取经过 transform 处理后的值

字段操作

  • setFields(fields: { name: NamePath; value?: any; touched?: boolean }[]) — 批量设置字段值和 touched 状态
  • getFields(nameList?: NamePath[]): { name: NamePath; value: any; touched: boolean; errors: string[] }[] — 获取字段完整信息
  • resetFields(nameList?: NamePath[]) — 重置字段到初始值并清除 touched 状态,不传参重置所有

校验

  • validateFields(nameList?: NamePath[]): Promise<void | { name: NamePath; errors: string[] }[]> — 校验字段,通过返回 undefined,失败返回错误数组
  • setFieldError(name: NamePath, errors: string[]) — 手动设置字段错误
  • getFieldError(name: NamePath): string[] — 获取字段错误
  • isFieldsTouched(nameList?: NamePath[], options?: { allTouched?: boolean }): boolean — 检查字段是否被修改过,allTouchedtrue 时要求全部被修改

提交与重置

  • submit(): Promise<Record<string, any> | undefined> — 触发校验并提交,校验通过返回表单值并触发 onFinish,失败返回 undefined 并触发 onFinishFailed
  • reset() — 重置所有字段

快照

  • capture(): FormSnapshot — 捕获当前表单完整状态(数据 + touched + errors),返回一个可序列化的对象
  • restore(snapshot: FormSnapshot) — 从快照恢复表单状态

capture 返回的对象可以用 JSON.stringify 序列化后存入 localStorage 等持久化存储,也可以直接保存在变量中。restore 会自动处理字段集合不一致的情况(快照中有但当前未注册的字段会在后续注册时自动恢复)。

校验规则(Rule)

Form.Item 的 rules 接受以下四种规则,可混合使用:

必填

{ required: boolean; message: string }

正则

{ pattern: RegExp; message: string }

值非空时校验,空值不触发。

长度/大小

{ min?: number; max?: number; message: string }

number 类型比较数值大小,其他类型比较 .length。值非空时校验。

自定义

{ validator: (value: any) => Promise<void | string> }

返回 string 表示错误信息,返回 void / undefined 表示通过。

useFormContext

在 Form 内部的任意组件中获取表单上下文:

import { useFormContext } from "taro-form-react";

const MyComponent = () => {
  const { data, setFieldValue, getFieldValue } = useFormContext();
  // ...
};

返回值同 FormContextProps,包含所有表单操作方法。必须在 <Form> 内部使用,否则会抛出异常。

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors