一、缘起为什么我要给 DialogBox 加上resizable能力说起来挺有意思的。作为一名在企业级应用开发一线摸爬滚打多年的前端DialogBox 这个组件我用了不下百次。但每次用的时候总觉得差点意思——用户想自己调整弹窗大小不好意思不支持。直到我参加了 OpenTiny NEXT 前端智能化系列直播听到老师讲AI Agent和WebMCP的时候我突然意识到这不就是我一直在等的那个契机吗传统的组件开发模式是开发者定义好所有功能用户只能被动接受。但在 AI 时代组件应该是可对话的——用户说我想把这个弹窗调大一点AI 就能理解意图并调用相应的 API。但要实现这个愿景首先得让组件具备足够的能力。所以我决定从最基础的开始为 TinyVue 的 DialogBox 组件实现真正的 resizable 功能。这不仅是功能增强更是为未来的 WebAgent 交互打下基础。二、实战深度解析 Renderless 架构的开发体验2.1 第一次扫描源码的震撼说实话刚开始看 TinyVue 源码时我被它的架构设计惊艳到了。以前我接触过的组件库比如 Element、Ant Design Vue都是把逻辑和视图混在一起的。但 TinyVue 完全不同它采用了Renderless 架构——核心思想就一个逻辑和视图彻底分离。怎么理解这个架构我用大白话解释一下传统架构就像一个厨师既要负责炒菜业务逻辑又要负责摆盘UI 渲染。结果就是换个盘子比如从 PC 端换到移动端就得重新学一遍炒菜。Renderless 架构则把厨师分成两个角色逻辑厨师只负责炒菜不管摆盘对应vue.ts文件摆盘师傅只负责摆盘不会炒菜对应pc.vue、mobile.vue文件这样做的好处是什么AI 友好的秘密就在这里AI 最擅长的是生成纯逻辑代码炒菜但不太理解复杂的 HTML/CSS摆盘。Renderless 架构正好让 AI 专注于它擅长的部分这就大大提高了 AI 生成代码的准确性。2.2 实现 resizable 的核心思路第一步理解 OpenTiny 的组件结构在动手之前我得先搞清楚 TinyVue 的 DialogBox 是怎么组织的。通过查看源码我发现它的文件结构是这样的dialog-box/ ├── src/ │ ├── pc.vue # PC 端的 UI 表现 │ └── mobile.vue # 移动端的 UI 表现 ├── vue.ts # 核心逻辑层所有平台共用 └── types.ts # 类型定义这意味着什么意味着我只需要在vue.ts中添加逻辑然后在pc.vue和mobile.vue中添加对应的 UI 句柄就能实现跨平台的 resizable 功能。这就是 OpenTiny 的第一个 Web 能力一次开发多端复用。操作如下环境搭建安装 Node.js 18安装 pnpm 9$ npm install -g pnpm使用 CLI 创建低代码平台Create low-code platform$ npx opentiny/tiny-engine-clilatest create-platformEnter the low-code platform directory$ cdInstall dependencies$ pnpm install第二步设计 resizable 的数据流要实现拖拽缩放我需要设计一套完整的数据流。让我用流程图来说明用户按下鼠标 → 记录起始位置和初始尺寸 → 监听鼠标移动 → 计算偏移量 → 更新尺寸 → 触发事件 ↓ ↓ 开始 resize 结束 resize基于这个流程我设计了以下几个关键状态// 在 vue.ts 中添加的状态resizing:false,// 标记是否正在缩放resizeDirection:,// 缩放方向东南西北、东南、东北等 8 个方向startX:0,// 鼠标按下的 X 坐标startY:0,// 鼠标按下的 Y 坐标startWidth:0,// 初始宽度startHeight:0,// 初始高度minWidth:200,// 最小宽度限制minHeight:100// 最小高度限制为什么要记录这么多状态想象一下你拉橡皮筋的过程你需要记住起点在哪里startX/startY橡皮筋原来的长度startWidth/startHeight然后才能根据当前手的位置计算出新的长度。resize 也是同样的道理。第三步实现核心的三个函数在 OpenTiny 的 Renderless 架构中我把 resizable 逻辑封装成了三个函数。这三个函数的设计思路非常清晰1. handleResizeStart开始拖拽consthandleResizeStart(direction)(event){// 1. 检查是否允许 resize比如全屏状态下不允许if(!props.resize||state.isFull)return// 2. 记录初始状态state.resizingtruestate.resizeDirectiondirection state.startXevent.clientX state.startYevent.clientY// 获取当前尺寸作为基准// 3. 添加全局监听这样即使鼠标移出元素也能继续跟踪document.addEventListener(mousemove,handleResizeMove)document.addEventListener(mouseup,handleResizeEnd)}为什么要添加到 document 而不是 element这是个很重要的细节。如果只监听元素本身当用户快速拖动鼠标超出元素范围时拖拽就会中断。添加到 document 上就能保证整个拖拽过程的连续性。2. handleResizeMove拖拽中consthandleResizeMove(event){if(!state.resizing)return// 1. 计算鼠标移动的偏移量constdeltaXevent.clientX-state.startXconstdeltaYevent.clientY-state.startY// 2. 根据拖拽方向计算新尺寸letnewWidthstate.startWidthletnewHeightstate.startHeightif(direction.includes(e)){// 向东右拖拽newWidthMath.max(state.minWidth,state.startWidthdeltaX)}if(direction.includes(s)){// 向南下拖拽newHeightMath.max(state.minHeight,state.startHeightdeltaY)}// ... 其他方向类似处理// 3. 直接修改 DOM 元素的样式dialog.style.width${newWidth}pxdialog.style.height${newHeight}px// 4. 触发事件让外部知道尺寸变化emit(resize-move,{width:newWidth,height:newHeight})}这里体现了 OpenTiny 的第二个 Web 能力精确的事件控制。注意看我使用了Math.max(state.minWidth, ...)这是为了防止用户把窗口缩得太小。这种边界检查在实际开发中非常重要能避免很多用户体验问题。3. handleResizeEnd结束拖拽consthandleResizeEnd(){if(!state.resizing)return// 1. 重置状态state.resizingfalsestate.resizeDirection// 2. 移除监听器非常重要防止内存泄漏document.removeEventListener(mousemove,handleResizeMove)document.removeEventListener(mouseup,handleResizeEnd)// 3. 触发结束事件emit(resize-end,{width,height})}为什么要移除监听器这是一个新手容易犯的错误。如果不移除组件销毁后监听器还在就会导致内存泄漏。你可以把它想象成你租了个房子添加监听器搬走时组件销毁一定要退租移除监听器否则房东浏览器会继续收你的钱占用内存。第四步在 UI 层添加拖拽句柄逻辑写好了接下来要在 UI 上让用户能够操作。我在pc.vue中添加了 8 个方向的拖拽句柄template div refdialog classtiny-dialog-box !-- 原有的弹窗内容 -- !-- 新增8 个方向的拖拽句柄 -- template v-ifresize !isFull !-- 四个角 -- div classresize-handle-nw mousedownhandleResizeStart(nw)/div div classresize-handle-ne mousedownhandleResizeStart(ne)/div div classresize-handle-sw mousedownhandleResizeStart(sw)/div div classresize-handle-se mousedownhandleResizeStart(se)/div !-- 四个边 -- div classresize-handle-n mousedownhandleResizeStart(n)/div div classresize-handle-s mousedownhandleResizeStart(s)/div div classresize-handle-w mousedownhandleResizeStart(w)/div div classresize-handle-e mousedownhandleResizeStart(e)/div /template /div /template为什么是 8 个方向这是为了给用户最大的自由度。可以只拉宽度东西方向、只拉高度南北方向或者同时拉宽高东南、东北等对角线方向。每个方向的 cursor 样式也不同给用户明确的视觉反馈。这里体现了 OpenTiny 的第三个 Web 能力灵活的 UI 定制。因为逻辑和 UI 分离我可以轻松地为不同平台定制不同的 UI。比如在 PC 端显示 8 个句柄在移动端可能只需要显示 4 个角的句柄节省屏幕空间。2.3 踩坑实录那些让我头疼的问题坑 1内存泄漏的教训刚开始实现时我犯了一个低级错误——忘记清理事件监听器。结果就是组件销毁后mousemove 事件还在触发导致内存泄漏。后来我添加了 onBeforeUnmount 钩子来兜底onBeforeUnmount((){// 确保组件销毁时清理所有监听器document.removeEventListener(mousemove,handleResizeMove)document.removeEventListener(mouseup,handleResizeEnd)})OpenTiny 的第四个 Web 能力完善的生命周期管理。Vue 提供了完整的生命周期钩子让我们能在合适的时机做清理工作。这是编写高质量组件的基本功。坑 2CSS 单位的精度问题一开始我用offsetWidth获取尺寸发现会有小数位丢失的问题。比如实际宽度是 500.7pxoffsetWidth 返回 501。解决方案是用getComputedStyleconstcomputedStylegetComputedStyle(dialog)constcurrentWidthparseFloat(computedStyle.width)// 精确值为什么会这样offsetWidth返回的是整数四舍五入后的像素值而getComputedStyle返回的是精确的计算值。在连续拖拽的场景中这种精度丢失会累积导致明显的抖动。坑 3移动端的兼容性问题在 PC 上测试完美一到移动端就歇菜。原因很简单移动端没有 mouse 事件只有 touch 事件。我最开始的做法是写两套逻辑// 鼠标事件element.addEventListener(mousedown,handleMouseDown)// 触摸事件element.addEventListener(touchstart,handleTouchStart)但这样代码量直接翻倍后来我发现了一个更好的方案——Pointer Events。Pointer Events 是一个 W3C 标准统一了鼠标、触摸、手写笔的输入事件// 一套代码搞定所有输入设备element.addEventListener(pointerdown,handlePointerDown)element.addEventListener(pointermove,handlePointerMove)element.addEventListener(pointerup,handlePointerUp)这样代码量直接减少了 40%这就是 OpenTiny 的第五个 Web 能力拥抱 Web 标准。三、思考从 resizable 到 WebAgent 的技术演进3.1 为什么要做这件事做到这里你可能要问不就是给弹窗加个缩放功能吗值得这么大费周章还真不是。我想做的是为未来的WebAgent 交互做准备。想象一下这个场景用户对 AI 说“帮我把这个弹窗调大一点里面的表格看不全”如果没有 resizable 能力AI 只能束手无策。但现在AI 可以通过WebMCP协议获取 DialogBox 的能力清单识别到resize方法可用自动调用handleResize或修改width/height属性完成用户的指令这就是GenUI生成式 UI的核心理念UI 不再是静态的而是可以根据用户意图动态调整的。3.2 WebMCP智能体与组件的翻译官通过这次实践我对WebMCPModel Context Protocol for Web有了更深的理解。简单来说WebMCP 就是让 AI 能够理解 Web 组件的能力。怎么做到每个组件需要暴露一份能力清单{component:DialogBox,version:1.0.0,capabilities:{methods:[open,close,resize],properties:{width:{type:string,writable:true,description:弹窗宽度},height:{type:string,writable:true,description:弹窗高度},resize:{type:boolean,readonly:true,description:是否支持缩放},isFull:{type:boolean,readonly:true,description:是否全屏}},events:[resize-move,resize-end]}}有了这份清单AI 就知道✅ 可以调用resize方法✅ 可以修改width和height❌ 不能在全屏状态下缩放因为有isFull限制这才是真正的人机对话基础。构建代码如下3.3 Renderless 架构对 AI 友好的秘密经过这次实战我发现Renderless 架构简直就是为 AI 而生的逻辑纯净AI 最擅长生成纯函数式的逻辑代码不需要理解 DOM类型完备TypeScript 类型定义让 AI 生成的代码更准确职责单一逻辑层只管状态和 API表现层只管渲染AI 不容易出错易于测试纯函数更容易编写单元测试AI 可以自动生成测试用例我甚至有个大胆的想法未来的组件库可能会专门为 AI 优化架构设计。四、感悟开发者如何在 AI 时代找到新定位4.1 我的角色转变这次实践让我真切感受到开发者的角色真的在变。以前的我花 2 小时写 CRUD 代码和表单验证花 1 小时调试事件监听的边界问题花 30 分钟写注释和文档剩下时间用来开会和沟通需求现在的我花 10 分钟跟 AI 描述需求花 20 分钟审查 AI 生成的代码花 30 分钟优化架构和性能剩下时间用来思考业务创新和用户体验这不是炫耀而是实实在在的效率提升。同样一个 resizable 功能如果完全手写我至少需要半天。但借助 AI我只用了 2 小时就完成了而且代码质量更高因为 AI 不会漏掉边界检查。4.2 什么应该交给 AI什么必须自己把控这是我的经验总结✅ 放心交给 AI样板代码getter/setter、事件处理框架单元测试AI 很擅长根据代码生成测试用例类型定义TypeScript interface/type代码格式化和小重构查找 Bug 的可能原因⚠️ 需要审核业务逻辑的正确性性能优化方案错误处理策略兼容性处理❌ 必须自己决策架构设计和技术选型用户体验细节安全合规问题技术债务的取舍记住一句话AI 是最好的副驾驶但方向盘必须在你手里。4.3 参与开源的真实收获很多人问我你为什么要花时间参与 OpenTiny 开源项目我的回答很实在技术层面真正理解了 Renderless 架构的设计精髓学会了如何编写对 AI 友好的代码掌握了企业级组件库的开发规范思维层面从使用者变成贡献者视角完全不同开始思考组件的通用性和扩展性理解了 API 设计的重要性职业发展在 GitHub/AtomGit 上留下了实实在在的贡献记录认识了一群优秀的开源开发者获得了官方颁发的贡献者证书当然还有各种周边奖品这个真的很香。但最重要的是我感觉自己不是在卷而是在创造价值。我写的每一行代码都可能被成千上万的开发者使用我设计的每一个功能都可能成为未来 AI 交互的基础设施。这种成就感比涨薪还让人兴奋。五、展望前端智能化的下一步5.1 我接下来的计划这次 resizable 实践只是个开始。我已经在规划下一步完善 resizable 功能添加保持宽高比选项支持拖拽到边缘自动吸附添加动画过渡效果探索 WebMCP 集成为 DialogBox 编写 MCP Schema实现 AI 可调用的标准接口与大模型平台对接测试输出最佳实践写一篇《如何用 AI 高效开发组件》的教程录制一个完整的实战视频在团队内部分享经验5.2 给同行的建议如果你也想在前端智能化领域有所建树我的建议是别观望先动手找个真实的开源项目提交你的第一个 PR善用 AI 工具GitHub Copilot、Cursor、通义灵码选一个顺手的深入理解架构不要满足于会用 API要搞懂背后的设计思想保持开放心态新技术层出不穷快速学习才是核心竞争力拥抱开源社区一个人的力量有限一群人才能走得更远六、写在最后前端智能化不是口号而是正在发生的现实。从辅助编码到智能体自主执行这条路可能还需要走 3 年、5 年甚至 10 年。但每一步前进都需要我们这一代开发者去铺路。我很庆幸自己能参与到这场变革中来。用代码为 AI 时代的基础设施添砖加瓦这本身就是件很酷的事情不是吗与所有在前端智能化路上探索的朋友共勉。参考资源项目源码OpenTiny TinyVue - DialogBox 组件OpenTiny TinyEngine 低代码引擎
从 Renderless 架构到 WebAgent:我的 OpenTiny 前端智能化实战之路
发布时间:2026/6/16 22:53:26
一、缘起为什么我要给 DialogBox 加上resizable能力说起来挺有意思的。作为一名在企业级应用开发一线摸爬滚打多年的前端DialogBox 这个组件我用了不下百次。但每次用的时候总觉得差点意思——用户想自己调整弹窗大小不好意思不支持。直到我参加了 OpenTiny NEXT 前端智能化系列直播听到老师讲AI Agent和WebMCP的时候我突然意识到这不就是我一直在等的那个契机吗传统的组件开发模式是开发者定义好所有功能用户只能被动接受。但在 AI 时代组件应该是可对话的——用户说我想把这个弹窗调大一点AI 就能理解意图并调用相应的 API。但要实现这个愿景首先得让组件具备足够的能力。所以我决定从最基础的开始为 TinyVue 的 DialogBox 组件实现真正的 resizable 功能。这不仅是功能增强更是为未来的 WebAgent 交互打下基础。二、实战深度解析 Renderless 架构的开发体验2.1 第一次扫描源码的震撼说实话刚开始看 TinyVue 源码时我被它的架构设计惊艳到了。以前我接触过的组件库比如 Element、Ant Design Vue都是把逻辑和视图混在一起的。但 TinyVue 完全不同它采用了Renderless 架构——核心思想就一个逻辑和视图彻底分离。怎么理解这个架构我用大白话解释一下传统架构就像一个厨师既要负责炒菜业务逻辑又要负责摆盘UI 渲染。结果就是换个盘子比如从 PC 端换到移动端就得重新学一遍炒菜。Renderless 架构则把厨师分成两个角色逻辑厨师只负责炒菜不管摆盘对应vue.ts文件摆盘师傅只负责摆盘不会炒菜对应pc.vue、mobile.vue文件这样做的好处是什么AI 友好的秘密就在这里AI 最擅长的是生成纯逻辑代码炒菜但不太理解复杂的 HTML/CSS摆盘。Renderless 架构正好让 AI 专注于它擅长的部分这就大大提高了 AI 生成代码的准确性。2.2 实现 resizable 的核心思路第一步理解 OpenTiny 的组件结构在动手之前我得先搞清楚 TinyVue 的 DialogBox 是怎么组织的。通过查看源码我发现它的文件结构是这样的dialog-box/ ├── src/ │ ├── pc.vue # PC 端的 UI 表现 │ └── mobile.vue # 移动端的 UI 表现 ├── vue.ts # 核心逻辑层所有平台共用 └── types.ts # 类型定义这意味着什么意味着我只需要在vue.ts中添加逻辑然后在pc.vue和mobile.vue中添加对应的 UI 句柄就能实现跨平台的 resizable 功能。这就是 OpenTiny 的第一个 Web 能力一次开发多端复用。操作如下环境搭建安装 Node.js 18安装 pnpm 9$ npm install -g pnpm使用 CLI 创建低代码平台Create low-code platform$ npx opentiny/tiny-engine-clilatest create-platformEnter the low-code platform directory$ cdInstall dependencies$ pnpm install第二步设计 resizable 的数据流要实现拖拽缩放我需要设计一套完整的数据流。让我用流程图来说明用户按下鼠标 → 记录起始位置和初始尺寸 → 监听鼠标移动 → 计算偏移量 → 更新尺寸 → 触发事件 ↓ ↓ 开始 resize 结束 resize基于这个流程我设计了以下几个关键状态// 在 vue.ts 中添加的状态resizing:false,// 标记是否正在缩放resizeDirection:,// 缩放方向东南西北、东南、东北等 8 个方向startX:0,// 鼠标按下的 X 坐标startY:0,// 鼠标按下的 Y 坐标startWidth:0,// 初始宽度startHeight:0,// 初始高度minWidth:200,// 最小宽度限制minHeight:100// 最小高度限制为什么要记录这么多状态想象一下你拉橡皮筋的过程你需要记住起点在哪里startX/startY橡皮筋原来的长度startWidth/startHeight然后才能根据当前手的位置计算出新的长度。resize 也是同样的道理。第三步实现核心的三个函数在 OpenTiny 的 Renderless 架构中我把 resizable 逻辑封装成了三个函数。这三个函数的设计思路非常清晰1. handleResizeStart开始拖拽consthandleResizeStart(direction)(event){// 1. 检查是否允许 resize比如全屏状态下不允许if(!props.resize||state.isFull)return// 2. 记录初始状态state.resizingtruestate.resizeDirectiondirection state.startXevent.clientX state.startYevent.clientY// 获取当前尺寸作为基准// 3. 添加全局监听这样即使鼠标移出元素也能继续跟踪document.addEventListener(mousemove,handleResizeMove)document.addEventListener(mouseup,handleResizeEnd)}为什么要添加到 document 而不是 element这是个很重要的细节。如果只监听元素本身当用户快速拖动鼠标超出元素范围时拖拽就会中断。添加到 document 上就能保证整个拖拽过程的连续性。2. handleResizeMove拖拽中consthandleResizeMove(event){if(!state.resizing)return// 1. 计算鼠标移动的偏移量constdeltaXevent.clientX-state.startXconstdeltaYevent.clientY-state.startY// 2. 根据拖拽方向计算新尺寸letnewWidthstate.startWidthletnewHeightstate.startHeightif(direction.includes(e)){// 向东右拖拽newWidthMath.max(state.minWidth,state.startWidthdeltaX)}if(direction.includes(s)){// 向南下拖拽newHeightMath.max(state.minHeight,state.startHeightdeltaY)}// ... 其他方向类似处理// 3. 直接修改 DOM 元素的样式dialog.style.width${newWidth}pxdialog.style.height${newHeight}px// 4. 触发事件让外部知道尺寸变化emit(resize-move,{width:newWidth,height:newHeight})}这里体现了 OpenTiny 的第二个 Web 能力精确的事件控制。注意看我使用了Math.max(state.minWidth, ...)这是为了防止用户把窗口缩得太小。这种边界检查在实际开发中非常重要能避免很多用户体验问题。3. handleResizeEnd结束拖拽consthandleResizeEnd(){if(!state.resizing)return// 1. 重置状态state.resizingfalsestate.resizeDirection// 2. 移除监听器非常重要防止内存泄漏document.removeEventListener(mousemove,handleResizeMove)document.removeEventListener(mouseup,handleResizeEnd)// 3. 触发结束事件emit(resize-end,{width,height})}为什么要移除监听器这是一个新手容易犯的错误。如果不移除组件销毁后监听器还在就会导致内存泄漏。你可以把它想象成你租了个房子添加监听器搬走时组件销毁一定要退租移除监听器否则房东浏览器会继续收你的钱占用内存。第四步在 UI 层添加拖拽句柄逻辑写好了接下来要在 UI 上让用户能够操作。我在pc.vue中添加了 8 个方向的拖拽句柄template div refdialog classtiny-dialog-box !-- 原有的弹窗内容 -- !-- 新增8 个方向的拖拽句柄 -- template v-ifresize !isFull !-- 四个角 -- div classresize-handle-nw mousedownhandleResizeStart(nw)/div div classresize-handle-ne mousedownhandleResizeStart(ne)/div div classresize-handle-sw mousedownhandleResizeStart(sw)/div div classresize-handle-se mousedownhandleResizeStart(se)/div !-- 四个边 -- div classresize-handle-n mousedownhandleResizeStart(n)/div div classresize-handle-s mousedownhandleResizeStart(s)/div div classresize-handle-w mousedownhandleResizeStart(w)/div div classresize-handle-e mousedownhandleResizeStart(e)/div /template /div /template为什么是 8 个方向这是为了给用户最大的自由度。可以只拉宽度东西方向、只拉高度南北方向或者同时拉宽高东南、东北等对角线方向。每个方向的 cursor 样式也不同给用户明确的视觉反馈。这里体现了 OpenTiny 的第三个 Web 能力灵活的 UI 定制。因为逻辑和 UI 分离我可以轻松地为不同平台定制不同的 UI。比如在 PC 端显示 8 个句柄在移动端可能只需要显示 4 个角的句柄节省屏幕空间。2.3 踩坑实录那些让我头疼的问题坑 1内存泄漏的教训刚开始实现时我犯了一个低级错误——忘记清理事件监听器。结果就是组件销毁后mousemove 事件还在触发导致内存泄漏。后来我添加了 onBeforeUnmount 钩子来兜底onBeforeUnmount((){// 确保组件销毁时清理所有监听器document.removeEventListener(mousemove,handleResizeMove)document.removeEventListener(mouseup,handleResizeEnd)})OpenTiny 的第四个 Web 能力完善的生命周期管理。Vue 提供了完整的生命周期钩子让我们能在合适的时机做清理工作。这是编写高质量组件的基本功。坑 2CSS 单位的精度问题一开始我用offsetWidth获取尺寸发现会有小数位丢失的问题。比如实际宽度是 500.7pxoffsetWidth 返回 501。解决方案是用getComputedStyleconstcomputedStylegetComputedStyle(dialog)constcurrentWidthparseFloat(computedStyle.width)// 精确值为什么会这样offsetWidth返回的是整数四舍五入后的像素值而getComputedStyle返回的是精确的计算值。在连续拖拽的场景中这种精度丢失会累积导致明显的抖动。坑 3移动端的兼容性问题在 PC 上测试完美一到移动端就歇菜。原因很简单移动端没有 mouse 事件只有 touch 事件。我最开始的做法是写两套逻辑// 鼠标事件element.addEventListener(mousedown,handleMouseDown)// 触摸事件element.addEventListener(touchstart,handleTouchStart)但这样代码量直接翻倍后来我发现了一个更好的方案——Pointer Events。Pointer Events 是一个 W3C 标准统一了鼠标、触摸、手写笔的输入事件// 一套代码搞定所有输入设备element.addEventListener(pointerdown,handlePointerDown)element.addEventListener(pointermove,handlePointerMove)element.addEventListener(pointerup,handlePointerUp)这样代码量直接减少了 40%这就是 OpenTiny 的第五个 Web 能力拥抱 Web 标准。三、思考从 resizable 到 WebAgent 的技术演进3.1 为什么要做这件事做到这里你可能要问不就是给弹窗加个缩放功能吗值得这么大费周章还真不是。我想做的是为未来的WebAgent 交互做准备。想象一下这个场景用户对 AI 说“帮我把这个弹窗调大一点里面的表格看不全”如果没有 resizable 能力AI 只能束手无策。但现在AI 可以通过WebMCP协议获取 DialogBox 的能力清单识别到resize方法可用自动调用handleResize或修改width/height属性完成用户的指令这就是GenUI生成式 UI的核心理念UI 不再是静态的而是可以根据用户意图动态调整的。3.2 WebMCP智能体与组件的翻译官通过这次实践我对WebMCPModel Context Protocol for Web有了更深的理解。简单来说WebMCP 就是让 AI 能够理解 Web 组件的能力。怎么做到每个组件需要暴露一份能力清单{component:DialogBox,version:1.0.0,capabilities:{methods:[open,close,resize],properties:{width:{type:string,writable:true,description:弹窗宽度},height:{type:string,writable:true,description:弹窗高度},resize:{type:boolean,readonly:true,description:是否支持缩放},isFull:{type:boolean,readonly:true,description:是否全屏}},events:[resize-move,resize-end]}}有了这份清单AI 就知道✅ 可以调用resize方法✅ 可以修改width和height❌ 不能在全屏状态下缩放因为有isFull限制这才是真正的人机对话基础。构建代码如下3.3 Renderless 架构对 AI 友好的秘密经过这次实战我发现Renderless 架构简直就是为 AI 而生的逻辑纯净AI 最擅长生成纯函数式的逻辑代码不需要理解 DOM类型完备TypeScript 类型定义让 AI 生成的代码更准确职责单一逻辑层只管状态和 API表现层只管渲染AI 不容易出错易于测试纯函数更容易编写单元测试AI 可以自动生成测试用例我甚至有个大胆的想法未来的组件库可能会专门为 AI 优化架构设计。四、感悟开发者如何在 AI 时代找到新定位4.1 我的角色转变这次实践让我真切感受到开发者的角色真的在变。以前的我花 2 小时写 CRUD 代码和表单验证花 1 小时调试事件监听的边界问题花 30 分钟写注释和文档剩下时间用来开会和沟通需求现在的我花 10 分钟跟 AI 描述需求花 20 分钟审查 AI 生成的代码花 30 分钟优化架构和性能剩下时间用来思考业务创新和用户体验这不是炫耀而是实实在在的效率提升。同样一个 resizable 功能如果完全手写我至少需要半天。但借助 AI我只用了 2 小时就完成了而且代码质量更高因为 AI 不会漏掉边界检查。4.2 什么应该交给 AI什么必须自己把控这是我的经验总结✅ 放心交给 AI样板代码getter/setter、事件处理框架单元测试AI 很擅长根据代码生成测试用例类型定义TypeScript interface/type代码格式化和小重构查找 Bug 的可能原因⚠️ 需要审核业务逻辑的正确性性能优化方案错误处理策略兼容性处理❌ 必须自己决策架构设计和技术选型用户体验细节安全合规问题技术债务的取舍记住一句话AI 是最好的副驾驶但方向盘必须在你手里。4.3 参与开源的真实收获很多人问我你为什么要花时间参与 OpenTiny 开源项目我的回答很实在技术层面真正理解了 Renderless 架构的设计精髓学会了如何编写对 AI 友好的代码掌握了企业级组件库的开发规范思维层面从使用者变成贡献者视角完全不同开始思考组件的通用性和扩展性理解了 API 设计的重要性职业发展在 GitHub/AtomGit 上留下了实实在在的贡献记录认识了一群优秀的开源开发者获得了官方颁发的贡献者证书当然还有各种周边奖品这个真的很香。但最重要的是我感觉自己不是在卷而是在创造价值。我写的每一行代码都可能被成千上万的开发者使用我设计的每一个功能都可能成为未来 AI 交互的基础设施。这种成就感比涨薪还让人兴奋。五、展望前端智能化的下一步5.1 我接下来的计划这次 resizable 实践只是个开始。我已经在规划下一步完善 resizable 功能添加保持宽高比选项支持拖拽到边缘自动吸附添加动画过渡效果探索 WebMCP 集成为 DialogBox 编写 MCP Schema实现 AI 可调用的标准接口与大模型平台对接测试输出最佳实践写一篇《如何用 AI 高效开发组件》的教程录制一个完整的实战视频在团队内部分享经验5.2 给同行的建议如果你也想在前端智能化领域有所建树我的建议是别观望先动手找个真实的开源项目提交你的第一个 PR善用 AI 工具GitHub Copilot、Cursor、通义灵码选一个顺手的深入理解架构不要满足于会用 API要搞懂背后的设计思想保持开放心态新技术层出不穷快速学习才是核心竞争力拥抱开源社区一个人的力量有限一群人才能走得更远六、写在最后前端智能化不是口号而是正在发生的现实。从辅助编码到智能体自主执行这条路可能还需要走 3 年、5 年甚至 10 年。但每一步前进都需要我们这一代开发者去铺路。我很庆幸自己能参与到这场变革中来。用代码为 AI 时代的基础设施添砖加瓦这本身就是件很酷的事情不是吗与所有在前端智能化路上探索的朋友共勉。参考资源项目源码OpenTiny TinyVue - DialogBox 组件OpenTiny TinyEngine 低代码引擎