🌲系列一:跟着官方示例学习 @tanStack-form --- Simple
这篇并没有采用官方的示例 🥺,不过不用担心,代码会被托管在
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>
</>
)}
/>;
💥 每个字段能独立展示错误信息
我们希望错误信息能分别出现在各自的输入框下方,这样用户更容易理解。
🥥 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>
代码地址🔗:Gitee
Array Docs 地址🔗: @tanStack/react-table
🌱 延伸
除了 .pushValue() 和 .removeValue(),@tanStack/react-form 还提供了更多对数组操作的方法,例如 .insertValue(index, value) 插入指定位置,.moveValue(from, to) 进行项位置的调整等。这些 API 非常适合在表单中实现拖拽排序、动态分组、复制项等交互场景。