Domphy keyed 实现深度解析:基于 toState 的细粒度 patch-based UI 运行时(js-framework-benchmark)
发布时间:2026/9/28 3:29:33来源:尧图网络
性能测试开发者工具【免费下载链接】js-framework-benchmarkA comparison of the performance of a few popular javascript frameworks项目地址https://gitcode.com/gh_mirrors/js/js-framework-benchmark点击查看免费下载导读本文围绕 js-framework-benchmark 仓库中 frameworks/keyed/domphy 的 keyed 实现展开系统讲解 Domphy 这一无 JSX、无虚拟 DOM的 patch-based 通用 UI 运行时在真实基准场景中的惯用法如何用单个toState状态驱动整张表格、如何为每行建立独立标签状态、如何通过元素描述符element descriptor缓存让重排/删除时未变化的行跳过 patch。读完本文你将掌握 Domphy 的核心状态模型、keyed 协调器的工作原理以及如何在 js-framework-benchmark 框架中完成一次可复现的基准构建与运行。一、Domphy 是什么README 中的核心定位仓库中的 frameworks/keyed/domphy/README.md 给出了 Domphy 的一锤定音式定位Domphy is a patch-based, framework-agnostic UI runtime. No JSX, no virtual DOM: UIs are plain objects keyed by HTML tag and reactivity is listener-based (toState).这段描述可以拆解为四个关键事实Patch-based基于补丁Domphy 直接在真实 DOM 上执行最小化的补丁操作而不是像 React/Vue 那样先构建虚拟 DOM 树再 diffFramework-agnostic它不绑定任何 JSX 或模板编译链视图就是普通的 JavaScript 对象字面量UI 是以 HTML 标签为 key 的普通对象例如{ tr: [...], class: ... }即描述一个tr元素对象属性名tr、td、a、span直接映射到标签名属性值即子节点或属性响应式基于监听器状态由toState(...)创建读取时建立依赖订阅写入时触发订阅者重新执行对应的补丁逻辑。此外README 还明确了该实现采用框架惯用的细粒度风格idiomatic fine-grained style并列出四条设计要点这四条要点在 src/main.ts 中均有对应实现是理解整篇文章的骨架用一个keyed 的StateRow[]承载行列表每行的_key驱动 keyed 协调器每行拥有独立的 label 状态使更新每第 10 行只重渲染这些行的文本一个表格级别的selectedid 状态每行从它派生出dangerclass每行的元素描述符只创建一次并跨列表重渲染复用因此重排/删除时未变化的行会被跳过。二、目录与构建管线从 TypeScript 源码到 dist/main.js该实现的完整目录结构如下frameworks/keyed/domphy/ ├── README.md # 实现说明本文所依据的核心文档 ├── index.html # 基准页面外壳 ├── package.json # 依赖与构建脚本 ├── package-lock.json └── src/ └── main.ts # 全部实现代码唯一源码入口从 package.json 可以看到运行时依赖domphy/core版本^0.21.1package.json中的js-framework-benchmark.frameworkVersionFromPackage指向它基准结果中的版本号domphy-v0.21.1-keyed即由此推导开发依赖esbuild版本0.25.12负责打包项目被标记为 TypeScript 语言实现main指向dist/main.js。README 给出的构建命令是两步npm install npm run build-prod其背后对应package.json的两个脚本dev: esbuild src/main.ts --bundle --formatiife --outfiledist/main.js, build-prod: esbuild src/main.ts --bundle --minify --formatiife --outfiledist/main.js二者的差别仅在于build-prod增加了--minify。两个脚本共同点--bundle将domphy/core依赖内联进单一文件--formatiife生成可直接以script标签加载的立即执行函数输出到dist/main.js。这正是 index.html 中script srcdist/main.js/script所引用的产物页面只需一个#main挂载点。三、状态模型一个列表 State 一个选中 State在 src/main.ts 中全部应用状态只有两个const data toStateRow[]([]); const selected toStatenumber | null(null);data是带 key 的列表状态元素类型为Rowselected是表格级别的选中行 id 状态初始为null。Row的定义src/main.ts值得注意——它把行 id与标签状态分开interface Row { id: number; label: ReturnTypetypeof toStatestring; }每行内部嵌套一个独立的label: toStatestring(...)这正是 README 第 2 条per-row label states的直接体现。buildDatasrc/main.ts在创建行时用三组词表形容词、颜色、名词随机拼接生成标签文本并递增全局nextId作为行 id。四、六个基准操作每个操作如何触碰状态js-framework-benchmark 的 keyed 基准要求实现 6 类操作main.ts中每个操作都极其精简因为它们只是对两个状态的读写操作函数实现要点src/main.ts 行号Create 1k / Create 10krun(count)selected.set(null)后整体替换data.set(buildData(count))L115-L118Append 1kadd(count)取当前数组 concat 新数据后再整体 setL120-L122Update every 10thupdate()步长 10 遍历只对rows[i].label调用set(label !!!)L124-L130Clearclear()清空选中态与数据L132-L135Remove oneremove(row)若删除的是选中行先清selected再 filter 掉该行L137-L140Swap rowsswapRows()行数少于 999 直接返回交换next[1]与next[998]后整体 setL142-L150这里最值得展开的是update()它没有触碰data列表而是逐行修改每行内嵌的label状态。由于每行元素描述符中的标签文本是(l: any) row.label.get(l)见下文第五节只有被修改的 100 行每第 10 行对应的订阅者会被触发并执行 DOM 文本补丁其余 900 行完全不参与这正是 README 强调的update every 10th row re-renders only those rows text。同理remove()与swapRows()走的是列表整体替换路径此时 keyed 协调器依据每行的_key计算差异见第五节而不是粗暴地重建整个tbody。五、元素描述符一次创建、跨重渲染复用README 第 4 条是这套实现最精巧的部分row element descriptors are created once per row and reused across list re-renders, so unchanged rows skip patching on reorder/remove.在 Domphy 中视图就是普通对象。例如删除按钮图标被定义为模块级常量src/main.tsconst REMOVE_ICON: DomphyElement { span: null, class: glyphicon glyphicon-remove, aria-hidden: true, } as DomphyElement;每行的元素描述符则由elementFor(row)src/main.ts构建并用一个WeakMapRow, DomphyElementelementCache缓存const elementCache new WeakMapRow, DomphyElement(); function elementFor(row: Row): DomphyElement { let element elementCache.get(row); if (element) return element; element { tr: [ { td: row.id, class: col-md-1 }, { td: [ { a: (l: any) row.label.get(l), onClick: () selected.set(row.id), }, ], class: col-md-4, }, { td: [ { a: [REMOVE_ICON], onClick: () remove(row), }, ], class: col-md-1, }, { td: null, class: col-md-6 }, ], _key: row.id, class: (l: any) (selected.get(l) row.id ? danger : ), } as DomphyElement; elementCache.set(row, element); return element; }这段代码同时示范了 Domphy 描述符的全部形态标签即 keytr、td、a、span直接作为对象键静态文本/子元素直接写{ td: row.id, class: col-md-1 }表示单元格内容是静态的 id 文本动态内容用函数a: (l: any) row.label.get(l)是一个读取表达式——它在首次求值时建立label状态与 DOM 文本节点的订阅关系之后label.set(...)只触发这一处补丁参数l是 Domphy 在求值时传入的上下文引用事件直接挂在描述符上onClick: () selected.set(row.id)_key驱动 keyed 协调器_key: row.id让列表 diff 时能够精确匹配新旧行。注意行 id 与 label 状态都封装在Row对象里因此即使data.set()传入全新数组只要Row对象引用不变elementCache仍能命中同一个描述符未变化的行在重排/删除时直接被协调器跳过派生 class 也用读取表达式class: (l: any) (selected.get(l) row.id ? danger : )——选中状态变化时只有相关行的 class 补丁会被执行。elementCache选用WeakMap也很有讲究key 是Row对象当行从列表移除且无其他引用时其描述符可被垃圾回收避免长期占用内存这对内存类基准见第七节是有利的设计。六、页面脚手架与挂载页面结构与 krausest 系列实现的 index.html 保持一致注释中明确说明 DOM 结构 mirror 自frameworks/keyed/vanillajstr td.col-md-1 (id) td.col-md-4 a (label) td.col-md-1 a span.glyphicon-remove td.col-md-6 选中行tr.dangersrc/main.ts 的actionButton(id, label, onClick)是操作按钮的工厂函数它把按钮放进col-sm-6 smallpad容器并挂上基准约定的元素 idrun、runlots、add、update、clear、swaprows——这些 id 是 webdriver-ts 驱动脚本点击按钮的定位依据。顶层App描述符src/main.ts由三部分组成jumbotron 标题区、工具栏按钮区以及核心的表格{ table: [ { tbody: (l: any) data.get(l).map(elementFor), id: tbody, }, ], class: table table-hover table-striped test-data, },tbody的内容是列表读取表达式data.get(l).map(elementFor)。列表状态每次set后这个表达式会重新求值Domphy 的 keyed 协调器依据每个描述符的_key与旧列表做最小化 diff——新增行插入、删除行移除、重排行移动其余行保持原 DOM 节点不动。最后一行完成挂载new ElementNode(App).render(document.getElementById(main)!);src/main.ts 使用domphy/core导出的ElementNode将App描述符渲染进 index.html 中的div idmain/div。页面还通过link href/css/currentStyle.css引入仓库根目录 css/currentStyle.css保证 Bootstrap 风格表格与按钮的样式与基准一致。七、基准中的参与情况与产物规模在仓库的 webdriver-ts/results.json 中可以确认该实现以domphy-v0.21.1-keyed的名义参与了基准测试共覆盖 15 个 benchmark 条目01_run1k, 02_replace1k, 03_update10th1k_x16, 04_select1k, 05_swap1k, 06_remove-one-1k, 07_create10k, 08_create1k-after1k_x2, 09_clear1k_x8, 21_ready-memory, 22_run-memory, 25_run-clear-memory, 41_size-uncompressed, 42_size-compressed, 43_first-paint从中可以观察到基准对实现的两类要求交互类01-09逐一对应第五节的 6 个操作外加 select、replace 与create-after-clear等组合场景内存类21/22/25与体积类41/42检验运行时开销与产物大小。关于产物体积results.json中记录的两条真实数据可作为参考41_size-uncompressed49.5 KB42_size-compressed14.4 KB这组数据与构建管线直接相关esbuild 以--bundle --minify将src/main.ts与domphy/core合并为单文件dist/main.js压缩后仅 14.4 KB包含完整运行时与基准应用侧面印证了 Domphy 作为轻量级 UI 运行时的体量。八、实现要点小结为什么这套写法是idiomatic回到 README 开头的四句话再对照源码可以总结出这套 keyed 实现的设计闭环状态粒度决定重渲染粒度列表级data只处理增删排文本级label只处理内容更新selected只处理选中样式——三者互不干扰描述符稳定性是性能前提WeakMap缓存的元素描述符让 keyed 协调器在重排时拥有稳定的引用避免重建与全量 patch读取表达式即依赖声明(l) x.get(l)同时完成读值与订阅两件事这正是 Domphylistener-based reactivity的编程模型标签对象字面量即 UI无需 JSX 编译器与虚拟 DOM diff从 src/main.ts 的App对象到真实 DOM中间只隔一层ElementNode。如果要在本地复现并验证本实现按 README.md 中的说明执行即可cd frameworks/keyed/domphy npm install npm run build-prod随后通过 index.html 加载dist/main.js即可在浏览器中体验 Create 1,000 rows、Update every 10th row、Swap Rows 等全部基准交互。需要说明的是dist/main.js为构建产物仓库并未提交该文件构建前的目录中仅有src/main.ts作为源码入口。赞分享性能测试开发者工具【免费下载链接】js-framework-benchmarkA comparison of the performance of a few popular javascript frameworks项目地址https://gitcode.com/gh_mirrors/js/js-framework-benchmark点击查看免费下载相关推荐大众点评数据采集终极解决方案深度破解动态字体加密高效获取全站商家信息大众点评数据采集终极解决方案深度破解动态字体加密高效获取全站商家信息 探索大众点评数据采集的完整技术方案揭秘如何绕过复杂的反爬机制实现稳定高效的全站数据性能测试开发者工具CANN/asc-devkit SIMT sinpif函数sinpif 产品支持情况 ! npu950 id1 Ascend 950PR/Ascend 950DT支持 ! end id1 ! npuA人工智能深度学习算子库CANNAscend基于 Inferno JSX Flags 的应用级运行时优化与 Rollup 构建实践js-framework-benchmark 实例解析基于 Inferno JSX Flags 的应用级运行时优化与 Rollup 构建实践js framework benchmark 实例解析 在 js fr性能测试开发者工具上一篇5分钟为OBS添加本地AI字幕LocalVocal完全指南下一篇英雄联盟玩家的终极神器5分钟上手League Akari免费工具箱创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网