@neeloong/form
v0.29.0
Published
一个基于响应式数据绑定的表单渲染库
Maintainers
Readme
@neeloong/form
@neeloong/form 是一个基于响应式数据绑定的表单渲染库,目前使用 tc39/proposal-signals 的 polyfill实现状态管理。其模板语法结合了声明式指令和灵活的变量管理,支持复杂表单的动态渲染和交互。
import { Store, Layout, render } from '@neeloong/form';
/** @import { Schema } from '@neeloong/form'; */
const template = `
<input !value="a" !bind="$value" />
<input !bind="a" />
<ul +a=1>
<li !value="list" !enum>
<input !bind="b" />
<ul !value="c" +x="0">
<li !enum +b=alias$size +a=2 +k="++x">
<div !fragment !text="k"></div>
<button @click="a+=1">a+1:<span !fragment !text="a"></span></button>
<button @click="b+=1">b+1:<span !fragment !text="b"></span></button>
<input !bind=a type=number />
<div !fragment !text="$no"/>. <div !fragment !text="d">1</div> <div !fragment !text>1</div>
<button @click="$remove">移除</button>
<button @click="$upMove" :disabled="!$upMovable">上移</button>
<button @click="$downMove()" :disabled="!$downMovable">下移</button>
</li>
<li><button @click="$add(0)">添加</button><button @click="$add( - $size - 1)">添加(<span !fragment !text=" - $size - 1"></span>)</button></li>
</ul>
</li>
</ul>
`
/** @type {Layout.Options} */
const layoutOptions = {
createCalc: value => {
const fn = new Function('globalThis', `with(globalThis) { return ${value} }`);
fn.toString = () => value;
return /** @type {*} */(fn);
},
createEvent: value => {
const fn = new Function('$event', 'globalThis', `with(globalThis) { ${value} }`);
fn.toString = () => value;
return /** @type {*} */(fn);
},
}
/**
* @type {Schema}
*/
const schema = {
a: { type: 'int', disabled: true },
list: { array: true, props: {
b: { type: 'int', disabled: true },
c: { array: true, type: 'int' },
},
},
};
const defaultValue = {
a: 5,
list: {b: 2, c: [1,2,3,4,5]}
}
const layouts = Layout.parse(template, layoutOptions);
const store = Store.create(schema);
store.value = defaultValue
/** @type {Element} */
// @ts-ignore
const app = document.querySelector('#app');
render(store, layouts, app);
API
Layout.parse(template, options)解析模板字符串,返回布局配置。Store.create(schema)创建表单存储实例,绑定数据与 Schema。render(store, layouts, parent)将表单渲染到指定 DOM 元素。
具体定义类型定义文件
模板语法
基础语法
标签属性
- 指令属性:以
!开头,用于条件渲染、循环等。 - 增强指令:以
~开头,用于自定义功能扩展(如表单验证、动态行为)。 - 事件绑定:以
@开头,绑定事件处理函数。 - 属性绑定:以
:开头,绑定动态属性值。
表达式语法
直接写在属性值中,支持 JavaScript 表达式。
<div !text="a + b"></div>属性绑定
<div :id="dynamicId"></div>事件绑定
<button @click="handleClick($event)">点击</button>事件对象(如鼠标坐标、键盘键码)可通过 $event 获取。
<input @input="value = $event.target.value">数据绑定与变量
变量声明
显式变量(
+var):
在标签中声明局部变量,作用域仅限当前标签及子标签。<div +count="0"> <!-- 初始化 count 为 0 --> <button @click="count += 1" v-text="count"></button> </div>别名(
*alias):
为复杂表达式或字段设置别名,简化引用。<div *len="array$size" !text="len"></div> <!-- 别名 len 指向当前数组长度 -->计算值(
*computed):
动态计算表达式,值随依赖变量变化自动更新。<div *double="a * 2" !text="double"></div>字段变量(
field$value):
直接访问字段的原始值,例如:<input !value="a" !text="field$value"> <!-- field$value 是字段 field 的原始值 -->
2.2 变量优先级
变量优先级从高到低:
- 显式变量(
+var)与别名(*alias) - 计算值(
*computed) - 字段变量(
field$value) - 全局变量(通过
render的global参数传入)
条件渲染(!if !else)
- 基本用法:
<div !if="a > 0">条件成立</div> <div !else !if="b < 0">条件二成立</div> <div !else>其他情况</div> - 注意事项:
!else必须与同级的!if同级,否则会被忽略。- 嵌套条件需使用多个同级标签。
<div !if="a">
<div !else !if="b"></div> <!-- !else 未与前一个 !if 同级,会被忽略 -->
</div>循环与枚举渲染(!enum)
基础用法
<ul>
<li !enum="items" !text="$item"></li> <!-- items 是数组 -->
</ul>自定义排序
<ul>
<li !enum="object" !sort="$key" !text="$item"></li> <!-- object 是对象,将按照其键排序,渲染其值 -->
</ul>嵌套循环
<div !enum="outerArray" *outerLen="$size">
<div !enum="innerArray">
<span !text="outerLen + ' + ' + $size"></span> <!-- 外层长度 + 内层长度 -->
</div>
</div>循环中的变量作用域
每个循环项拥有独立作用域,变量互不影响。
<ul>
<li !enum="items" +count="0">
<button @click="count += 1" v-text="count"></button> <!-- 每个按钮独立计数 -->
</li>
</ul>子属性
<h1 !value="subField"></h1>字段绑定
<input !bind="a" !placeholder="请输入值" />- 双向绑定:字段值与表单数据同步更新。
片段(!fragment)
包裹多标签内容,形成作用域。
<span !fragment !if="a">
<p>内容1</p>
<p>内容2</p>
</span>!fragment 所在的标签不会被渲染。
文本渲染(!text) 与 html 渲染(!html)
!text:渲染纯文本
<div !text="a"></div>!html:渲染 HTML 内容(需确保内容安全)
<div !html="safeHTML"></div>若 !text 与 !html 同时存在,!html 会被忽略。
注释(!comment)
仅用于开发,对渲染无影响。
<div !comment="注释内容"></div>别名与计算值
<div *alias1="v1" *computed="v1 + v2"></div>显式变量(局部变量)
<div +var1="123"></div>增强指令(~)
自定义指令
通过参数注册自定义指令
- 使用增强指令:
增强指令顺序
按标签中首次出现的顺序执行。
<div ~a ~b ~c></div> <!-- 先执行 a,再 b,最后 c -->各用法的优先级
优先级从高到低:
- 模板定义:
!template - 条件:
!if!else - 子属性:
!value - 枚举:
!enum!sort - 别名、计算名与显式变量:
*别名*计算名+变量 - 片段与模板调用:
!fragment - 属性与事件:
:绑定属性@事件普通属性!bind - 子内容:
!text!html - 注释:
!comment
变量
- 字段扩展隐式属性
$value字段当前值$state字段状态$store只读 当前字段表单存储实例$schema只读 字段的 Schema 定义$null只读 是否为空元素$index只读 当前项的索引$no只读 数组项目的序号$size只读 数组的长度、对象的成员数$creatable只读 值是否可创建($new为true时,字段只读)$immutable只读 值是否不可改变($new为false时,字段只读)$new只读 是否新建项$loading只读 加载状态$readonly只读 是否只读$hidden只读 是否可隐藏$clearable只读 是否可清除$required只读 是否必填$disabled只读 是否禁用字段$label只读 字段的标签信息$description只读 字段的描述信息$placeholder只读 字段的占位符信息$min只读 数值字段的最小值限制$max只读 数值字段的最大值限制$step只读 数值字段的步长$minLength只读 最小长度$maxLength只读 最大长度$pattern只读 模式$values只读 可选值$type只读 字段类型$meta只读 字段元信息$component只读 自定义渲染组件信息$kind只读 字段类别$error只读 字段校验错误信息$errors只读 所有校验错误列表$addable只读 是否可以为当前数组增加项目,非数组上下文总是为false$removable只读 是否可以将当前项从数组中移除,非数组成员上下文总是为false
- 字段扩展隐式函数(只在事件中可用)
$reset()重置数据$validate()触发当前项的异步校验$validate(true)触发当前项及其后代的异步校验
- 数组字段扩展隐式函数(只在事件中可用)
$insert(index, value)在指定索引插入值$add(value):向数组末尾添加值。$remove(index)删除指定索引的项$move(from, to)移动数组项$exchange(a, b)切换 a b 两项
- 数组成员字段扩展隐式属性
$upMovable只读 是否可上移$downMovable只读 是否可下移
- 数组成员字段扩展隐式函数(只在事件中可用)
$remove()移除当前项$upMove()上移当前项$downMove()下移当前项
- 上下文隐式变量
$store只读 当前表单存储实例。$root只读 根上下文对象。
enum上下文新增的变量$$count枚举上下文的数量$$key枚举上下文的当前项的键$$index枚举上下文当前项的索引$$item枚举上下文当前项
- 当前范围及组件范围的所有字段(数组字段的成员除外,因为数组字段成员索引为数字,不符合标识符命名规则)都存在
field$value及field$$value形式的变量 - 如果别名为字段的别名(如
*alias="v"),则也存在alias$value形式的变量 - 当存在同名变量时,则会按照变量来源类型决定优先级,从高到低依次为:
- 上下文隐式变量
- 显式声明(如别名、计算名、显式变量),如果别名是字段的别名,则也包括
alias$value形式的变量 - 全局变量,此部分由
render的参数传入 - 字段声明,包括
field$value及field$$value形式的变量
- 在同一来源类型重复的变量名,再按照作用域处理
示例代码
嵌套表单与循环
<ul !enum="users">
<li !enum="user.orders">
<div !text="user.name"></div>
<div !text="order.amount"></div>
<button @click="$remove">删除</button>
</li>
</ul>条件渲染与字段绑定
<div !if="showForm">
<input !bind="username" !placeholder="用户名" />
<button @click="$submit">提交</button>
</div>
<div !else !text="暂无表单"></div>渲染一个带条件和循环的表单
<div>
<h1 !text="title"></h1>
<div !if="showForm">
<form !enum="fields">
<div !enum="items">
<input !bind="value" !placeholder="placeholder">
</div>
</form>
</div>
<div !else !text="暂无数据"></div>
<!-- 添加按钮 -->
<button @click="$add('new item')">添加项</button>
</div>