4、第一个FormalNode实例:创建简单的表单节点、定义基础属性、输出节点结构。

好,咱们直接动手。前面聊了那么多理论,现在该写点真东西了。

这一节,我会带你创建一个最简单的 FormalNode 实例。说白了,就是先搭一个表单的“骨架”,看看它到底长什么样。

4.1 从零开始:创建一个表单节点

我个人习惯,在开始一个新项目或新模块时,先不急着写复杂的逻辑。先定义一个最基础的对象,看看它的结构是否合理。

FormalNode 里,一个表单节点就是一个对象。它包含了这个字段的所有“元信息”。

来看代码:

// 创建一个最简单的表单节点
const userNameNode = {
  // 节点的唯一标识
  name: 'userName',
  // 节点的类型
  type: 'string',
  // 节点的标签(给用户看的)
  label: '用户名',
  // 节点的值
  value: '',
  // 节点的验证规则
  rules: [],
  // 节点的错误信息
  error: null,
  // 节点是否被修改过
  dirty: false,
  // 节点是否被触摸过(比如用户点击过输入框)
  touched: false
};

console.log(userNameNode);

你看,这就是一个最原始的节点。它没有依赖任何库,就是一个纯粹的 JavaScript 对象。

我在项目中遇到过不少新手,一上来就把所有逻辑塞进一个巨大的对象里,结果后期维护起来特别痛苦。所以,我建议你从一开始就把结构定义清楚。

4.2 定义基础属性:每个节点都该有的“身份证”

刚才那个例子里的属性,其实都是基础属性。咱们来拆解一下,每个属性是干嘛用的。

属性名 类型 说明
name string 节点的唯一标识。在整个表单里,不能重复。
type string 字段类型。比如 stringnumberemail 等。
label string 展示给用户的文本。比如“用户名”、“密码”。
value any 当前存储的值。初始为空字符串或 null
rules array 验证规则列表。每个规则是一个函数或对象。
error string | null 验证失败时的错误信息。没有错误就是 null
dirty boolean 用户是否修改过这个字段的值。
touched boolean 用户是否“触碰”过这个字段。比如点击输入框又离开。

嗯,这里要注意。很多表单库会把 dirtytouched 混为一谈。其实不一样。

  • dirty:值变了就算。哪怕你改了一个字符又改回来,它也是 true
  • touched:用户交互过就算。比如你点了输入框,但没输入任何内容,它也是 true

我曾经踩过一个坑:只判断 dirty 来决定是否显示错误。结果用户刚打开页面,什么都没做,错误就冒出来了。用户体验极差。后来我改成同时判断 touched,才解决了这个问题。

4.3 输出节点结构:看看它到底长什么样

定义好了,咱们把它打印出来看看。你想想看,一个真实的表单节点,在内存里到底是什么样子?

// 创建一个稍微丰富一点的节点
const emailNode = {
  name: 'email',
  type: 'email',
  label: '电子邮箱',
  value: 'test@example.com',
  rules: [
    { required: true, message: '邮箱不能为空' },
    { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: '邮箱格式不正确' }
  ],
  error: null,
  dirty: false,
  touched: false
};

// 输出节点结构
console.log(JSON.stringify(emailNode, null, 2));

输出结果是这样的:

{
  "name": "email",
  "type": "email",
  "label": "电子邮箱",
  "value": "test@example.com",
  "rules": [
    {
      "required": true,
      "message": "邮箱不能为空"
    },
    {
      "pattern": {},
      "message": "邮箱格式不正确"
    }
  ],
  "error": null,
  "dirty": false,
  "touched": false
}

看到没?rules 是一个数组,里面每个元素都是一个规则对象。每个规则对象至少包含一个 message 属性,用于在验证失败时提示用户。

这里有个小细节:pattern 在 JSON 序列化时变成了 {}。这是因为正则表达式对象在 JSON.stringify 时会被转换成空对象。如果你需要完整保留规则,建议用自定义的序列化方法。不过咱们现在只是看结构,不影响理解。

核心要点:一个 FormalNode 实例,本质上就是一个“自描述”的对象。它知道自己叫什么、是什么类型、当前值是什么、该怎么验证、有没有出错。

4.4 用 SVG 画一张节点结构图

光看代码可能还不够直观。我画了一张图,帮你把整个结构串起来。

FormalNode 实例结构 FormalNode 实例 name: "userName" type: "string" label: "用户名" value: "" rules: [...] error: null dirty: false | touched: false 每个节点都包含:标识、类型、值、规则、状态 这些属性共同构成了一个完整的表单字段描述

小技巧:在实际项目中,我习惯把 dirtytouched 合并成一个 status 枚举。比如 status: 'idle' | 'focused' | 'changed' | 'submitted'。这样状态更清晰,也更容易做条件判断。不过对于初学者,先分开理解会更直观。

4.5 为什么这样设计?

你可能会问:为什么非要搞这么多个属性?直接一个 value 不就行了吗?

其实,表单的核心痛点不是“存值”,而是“状态管理”。

  • 用户有没有改过?→ dirty
  • 用户有没有看过?→ touched
  • 验证过了没有?→ error
  • 该怎么验证?→ rules

这些状态,缺一个,你的表单体验就会出问题。我见过太多项目,因为没区分 dirtytouched,导致表单验证时机乱七八糟。

注意:不要试图把所有逻辑都塞进一个 value 里。比如有人喜欢用 value: { text: '', error: null, dirty: false }。这样虽然也能用,但后期扩展起来非常痛苦。每个属性各司其职,才是正道。

好了,第一个 FormalNode 实例就讲到这里。你现在应该能自己创建一个简单的表单节点,并且理解每个属性的含义了。下一节,我们会在这个基础上,给它加上验证逻辑。


公众号:蓝海资料掘金营,微信deep3321