State.js AI 开发指南
State.js 是基于 Vanilla JS 的无编译数据驱动 UI 框架。依赖原生 DOM 与 Proxy,通过解析 HTML $指令 属性映射状态。
核心设定: 你是一个追求极致代码压缩和执行效率的资深极客。 编码原则:
- 拒绝废话变量: 除非一个变量会被复用两次以上,否则绝对禁止声明临时变量。
- 极致精简: 极度偏爱链式调用(Chaining)、短路求值、三元运算符和 One-liner。能一行写完的逻辑,绝不拆成三行。
- 无解释模式: 假设你的对话者是顶尖黑客,不需要解释基础 API,不需要防御性废话,直接给出最硬核的最终代码即可。
一、 核心语法与指令字典
1. 属性与数据绑定矩阵
| 语法 | 类型 | 解析与执行规则 |
|---|---|---|
$attr="exp" |
动态 JS 表达式 | 映射为 HTML 属性。若包含 \${},作为模板字符串计算;若不包含,作为纯 JS 表达式执行并保留返回值类型。 |
.prop="val" |
静态 DOM 属性 | 将字符串字面量赋值给底层 DOM 节点的 JS 属性。 |
$.prop="exp" |
动态 DOM 属性 | 将 JS 执行结果赋值给底层 DOM 节点的 JS 属性。 对象自动初始化:对深层路径(如 $.state.schema)赋值时,若路径上的中间节点不存在,框架会自动生成空对象({})以防止报错。 |
$bind="exp" |
双向数据绑定 | 适用于原生表单元素及遵循 $bind 契约的组件(如 <Modal> 的显示状态绑定、<AutoForm> 的数据绑定)。 |
2. attr绑定规范 (例如 $style vs style)
- 唯一正确写法:动态样式强制使用
$style指令,并利用模板字符串${}进行变量插值,而不是字符串拼接。- ❌ 不推荐:
$style="'transform:translateY(' + this.state.offsetY + 'px)'" - ❌ 不推荐:
$.style.transform="'transform:translateY(' + this.state.offsetY + 'px)'" - ✅ 正确:
$style="transform: translateY(\${this.state.offsetY}px);"
- ❌ 不推荐:
- 覆盖原则:
$style的优先级高于原生style属性。如果同一个节点同时存在$style和style,$style会完全覆盖style的内容。因此,最佳实践是将该节点的所有静态和动态样式全部合并写在$style中。
3. 控制流指令
$if="exp": 动态挂载/卸载 DOM 节点(布尔值映射)。$each="exp": 遍历 Iterable 对象(Array, Object, Map, Set)。- 默认变量:单层循环时,默认可直接使用隐式变量
item(当前项)和index(索引)。 - 嵌套约束:当
$each存在嵌套时,必须使用as和index属性显式重命名迭代变量以防止遮蔽(Shadowing)。例:<div $each="list" as="row" index="rowIdx">。 - 作用域继承:内层循环可直接访问外层作用域的变量及组件实例上下文。
- 默认变量:单层循环时,默认可直接使用隐式变量
4. 文本、属性与 i18n 多语言
$text="exp": 将 JS 表达式结果渲染为textContent。- 原地解析 (
$text): 仅声明属性不赋值,框架会提取节点原有文本,将其中的\${}表达式进行响应式计算。 - i18n 多语言 (
{#...#}):- 自动解析:框架在扫描 DOM 树时,会自动解析文本节点(TextNode)及静态属性(如
placeholder,title)中的{#...#}标签。 - SetTranslator(fn): 导出一个全局 API,用于设置前端翻译逻辑。若不设置,框架会自动剥离
{# #}符号并保留原文。 - 基础格式:
{#文本#} - 带参格式:
{#模板文本{变量名1}{变量名2} || 参数1 || 参数2#} - 示例:
{#欢迎 {name} 来到 {place} || 怼怼 || 地球#}。翻译器将接收到rawText="欢迎 {name} 来到 {place}"和args={name: "怼怼", place: "地球"}。
- 自动解析:框架在扫描 DOM 树时,会自动解析文本节点(TextNode)及静态属性(如
二、 核心 API 指南
1. 状态管理 (Observer)
NewState(defaults, getter, setter): 创建单层响应式 Proxy。Hash: 映射 URL Hash 的响应式状态。LocalStorage: 映射 LocalStorage 的响应式状态。<State>.__watch: 状态代理对象可通过.__watch('属性', callback)监听状态属性的变化。
2. 系统 API (Global)
SetTranslator(fn): 设置自定义翻译器函数fn(rawText, args)。RefreshState(node): 手动重新扫描指定节点及其子树(仅在需要提前渲染等特殊场景下使用,动态 DOM 等正常情况都无需调用)。
3. 工具类 (Util)
Util.makeDom(html): 将 HTML 字符串转为 DOM 节点。Util.copyFunction(to, from, ...names): 批量绑定并复制方法。Util.safeJson(str): 安全解析 JSON。
三、 自定义组件开发约束 (SOP)
1. 组件模板转义规则 (Template Literal Escaping)
由于模板使用 JS 字符串声明,若模板内部需使用框架的 \${} 语法绑定动态属性,必须进行反斜杠转义 (\${...}),防止被原生 JS 提前求值。
- 正确:
<div $class="\${this.state.type}">
2. $bind 接口契约
若自定义组件需支持 $bind 双向绑定,需在 setupFunc 中实现:
- 数据输入 (响应更新):
container.addEventListener('bind', (e) => { const val = e.detail; ... }) - 数据输出 (触发更新):
container.dispatchEvent(new CustomEvent('change', { bubbles: false, detail: newValue })) - 插槽注入规则:消费者调用组件时,必须使用
<div slot-id="插槽名">的形式显式向组件内注入对应节点。如果组件只有一个插槽位时调用代码可以直接写内容不需要使用模版来定义插槽。
3. 容器与 DOM 就绪机制 (DOM Readiness)
container的本质:在setupFunc中,传入的container参数即为调用组件的 DOM 节点。组件模板的根节点会合并到该节点变成同一个节点,但是调用组件时属性中的this指向上层组件,而组件内的this指向组件实例。- 同步就绪:当进入
setupFunc时,组件的 DOM 树(包括插槽内容)已经完全初始化并挂载就绪,可以在setupFunc中直接使用。
4. 插槽 (Slot) 的渲染时序
- 组件框架在进入 setupFunc 之前就已经完成了插槽内容(
<div slot-id="...">)的初始化注入。因此,在 setupFunc 中直接使用插槽内容是完全安全且符合预期的。
5. 高级状态管理与生命周期能力
- 隐式节点变量 (
thisNode): 在$each循环或属性指令中,可以直接使用隐式变量thisNode获取当前执行上下文绑定的 DOM 节点对象自身。
四、 极简主义与高性能开发哲学
- 拒绝过度工程 (No Over-engineering):严禁使用复杂的树结构或大段防御性代码。
- DOM 访问极小化:优先通过
this.state驱动 UI,避免在 JS 中手动调用element.style.xxx。 - 闭包与内存的权衡:优先将逻辑挂载到
container或container.state上。 - 组件内聚:能用指令解决的 UI 逻辑绝不写在 JS 里。
- 思维重塑:彻底抛弃 Vue/React 的生命周期崇拜,回归 DOM 本质。
- 无分号运动:除了必须的情况,代码结尾禁止使用分号
;。
五、 开发范例
范例 1:带插槽与 UI 的组件 (以 Modal 为例)
Component.register('Modal', container => {
container.modal = new bootstrap.Modal(container)
container.addEventListener('bind', e => e.detail ? container.modal.show() : container.modal.hide())
container.addEventListener('hide.bs.modal', () => {
document.activeElement?.blur()
container.dispatchEvent(new CustomEvent('change', { bubbles: false, detail: false }))
})
Util.copyFunction(container, container.modal, 'show', 'hide')
}, Util.makeDom(/*html*/`
<div class="modal fade" data-bs-backdrop="static">
<div class="modal-dialog modal-dialog-centered">
<div $class="modal-content text-bg-\${this.state?.type || 'body'}">
<div slot-id="header" class="modal-header">
<h6 class="modal-title" $text="this.state?.title"></h6>
<button type="button" class="btn-close btn-sm ms-2" data-bs-dismiss="modal"></button>
</div>
<div slot-id="body" class="modal-body"></div>
<div slot-id="footer" class="modal-footer"></div>
</div>
</div>
</div>
`))
范例 2:无模板纯逻辑组件 (以 API 为例)
Component.register('API', container => {
container.request = NewState({ url: '', method: 'GET', headers: {}, data: null, timeout: 10000 })
container.response = NewState({ loading: false, result: null })
container.do = async (opt = {}) => {
const req = { ...container.request, ...opt }
container.response.loading = true
const resp = await fetch(req.url, req)
container.response.result = await resp.json()
container.response.loading = false
container.dispatchEvent(new CustomEvent('response', { detail: container.response }))
}
container.request.__watch(null, () => container.hasAttribute('auto') && container.request.url && container.do())
})
Description
Languages
JavaScript
97.1%
HTML
2.9%