跟着官方示例学习 @tanStack-form --- Array

215 阅读2分钟

🌲系列一:跟着官方示例学习 @tanStack-form --- Simple


这篇并没有采用官方的示例 🥺,不过不用担心,代码会被托管在 Gitee 上的

Gitee 地址 🔗

🧩 动态数组字段?用起来超简单!

在实际项目中,我们经常会遇到这样的场景:

✨ 用户可以动态添加一组重复项,比如输入多种技能,每项包含“语言 + 等级”。

这时我们就不能只用基本字段了,而是要进入 @tanStack/react-form 的一个强大功能区块:数组字段(Array Fields)

今天我们来实战演示一下,如何用 @tanStack/react-form 实现一个可动态添加和删除的技能列表表单。

🎯 目标效果

  • 用户可以随时点击“新增”添加技能

  • 每项技能都有语言和评分两个子字段

  • 用户可以删除任意一项

  • 提交时,收集整个数组的表单值

🧱 基础结构

先来看看我们最终的组件结构:

const form = useForm({
  defaultValues: {
    skills: [] as { language: string; rating: number }[],
  },
  onSubmit: ({ value }) => {
    console.log(value);
  },
});

🌈 使用 form.Field 渲染数组字段

核心在于:我们使用 mode="array" 声明这个字段是数组类型,并通过 field.state.value.map(...) 遍历渲染每个子项。

<form.Field
  name="skills"
  mode="array"
  children={(field) => (
    <>
      {field.state.value.map((_, index) => (
        <div key={index}>
          {/* 子字段 */}
          ...
          {/* 删除按钮 */}
          <button onClick={() => field.removeValue(index)}>删除</button>
        </div>
      ))}
      <button onClick={() => field.pushValue({ language: '', rating: 0 })}>
        新增
      </button>
    </>
  )}
/>

📒 渲染子字段

每个数组项本质上是一个对象,因此我们可以继续用 form.Field 渲染它的属性字段,比如:

<form.Field name={`skills[${index}].language`} children={(subField) => (
  <input
    type="text"
    value={subField.state.value}
    onChange={(e) => subField.handleChange(e.target.value)}
  />
)} />

skills[${index}].rating 同理。

这种方式与我们在普通字段中写法几乎一模一样,唯一变化是 name 是字符串路径。

🔔 注意事项

  • 每次点击“新增”按钮,会往数组尾部推入一个新项(通过 pushValue

  • 每项的 key 要用 index,避免重复

  • 删除操作是 removeValue(index),直接传索引

💻 获取用户填写信息

<form.Subscribe
  selector={(state) => [state.canSubmit, state.isSubmitting]}
  children={([canSubmit, isSubmitting]) => (
    <>
      <button type="submit" onClick={form.handleSubmit} disabled={!canSubmit}>
        {isSubmitting ? "..." : "Submit"}
      </button>
      <button
        type="reset"
        onClick={(e) => {
          e.preventDefault();
          form.reset();
        }}
      >
        Reset
      </button>
    </>
  )}
/>;

Jun-18-2025 15-16-00.gif

💥 每个字段能独立展示错误信息

我们希望错误信息能分别出现在各自的输入框下方,这样用户更容易理解。

🥥 useForm 配置:添加 Zod 校验器

const form = useForm({
  // ...
  validators: {
    onChange: ZodSchema,
  },
  // ...
})

zod 对数组字段进行嵌套验证 —— 数组元素是对象,对象再由字段组成,每一层都可以定义自己的规则。

const skillSchema = z.object({
  language: z.string().min(3, '语言至少 3 个字符'),
  rating: z.number().gt(0, '评分必须大于 0'),
})

const ZodSchema = z.object({
  skills: z.array(skillSchema).min(1, '请至少添加一个技能'),
})

🙅 错误信息显示

<form.Field name={`skills[${index}].language`}>
  {(subField) => (
    <>
      <input
        type="text"
        value={subField.state.value}
        onChange={(e) => subField.handleChange(e.target.value)}
      />
      <FieldInfo field={subField} />
    </>
  )}
</form.Field>

<form.Field name={`skills[${index}].rating`}>
  {(subField) => (
    <>
      <input
        type="number"
        value={subField.state.value}
        onChange={(e) => subField.handleChange(e.target.valueAsNumber)}
      />
      <FieldInfo field={subField} />
    </>
  )}
</form.Field>

Jun-18-2025 14-55-35.gif

代码地址🔗:Gitee

Array Docs 地址🔗: @tanStack/react-table

🌱 延伸

除了 .pushValue().removeValue()@tanStack/react-form 还提供了更多对数组操作的方法,例如 .insertValue(index, value) 插入指定位置,.moveValue(from, to) 进行项位置的调整等。这些 API 非常适合在表单中实现拖拽排序、动态分组、复制项等交互场景。

API 文档地址🔗