从 Renderless 架构到 WebAgent:我的 OpenTiny 前端智能化实战之路

从 Renderless 架构到 WebAgent:我的 OpenTiny 前端智能化实战之路

一、缘起:为什么我要给 DialogBox 加上"resizable"能力?

说起来挺有意思的。作为一名在企业级应用开发一线摸爬滚打多年的前端,DialogBox 这个组件我用了不下百次。但每次用的时候,总觉得差点意思——用户想自己调整弹窗大小?不好意思,不支持。

直到我参加了 OpenTiny NEXT 前端智能化系列直播,听到老师讲 AI AgentWebMCP 的时候,我突然意识到:这不就是我一直在等的那个契机吗?

传统的组件开发模式是:开发者定义好所有功能,用户只能被动接受。但在 AI 时代,组件应该是"可对话"的——用户说"我想把这个弹窗调大一点",AI 就能理解意图并调用相应的 API。

但要实现这个愿景,首先得让组件具备足够的能力。所以,我决定从最基础的开始:为 TinyVue 的 DialogBox 组件实现真正的 resizable 功能。这不仅是功能增强,更是为未来的 WebAgent 交互打下基础。

二、实战:深度解析 Renderless 架构的开发体验

2.1 第一次扫描源码的震撼

说实话,刚开始看 TinyVue 源码时,我被它的架构设计惊艳到了。

在这里插入图片描述


在这里插入图片描述

以前我接触过的组件库(比如 Element、Ant Design Vue),都是把逻辑和视图混在一起的。但 TinyVue 完全不同,它采用了 Renderless 架构——核心思想就一个:逻辑和视图彻底分离

怎么理解这个架构?我用大白话解释一下:

传统架构就像一个厨师,既要负责炒菜(业务逻辑),又要负责摆盘(UI 渲染)。结果就是换个盘子(比如从 PC 端换到移动端)就得重新学一遍炒菜。

Renderless 架构则把厨师分成两个角色:

  • 逻辑厨师:只负责炒菜,不管摆盘(对应 vue.ts 文件)
  • 摆盘师傅:只负责摆盘,不会炒菜(对应 pc.vuemobile.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.vuemobile.vue 中添加对应的 UI 句柄,就能实现跨平台的 resizable 功能。

这就是 OpenTiny 的第一个 Web 能力:一次开发,多端复用。

操作如下:
环境搭建
安装 Node.js 18+

安装 pnpm 9+

$ npm install -g pnpm

使用 CLI 创建低代码平台

Create low-code platform

$ npx @opentiny/tiny-engine-cli@latest create-platform

Enter the low-code platform directory

$ cd

Install 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.resizing =true state.resizeDirection = direction state.startX = event.clientX state.startY = event.clientY // 获取当前尺寸作为基准// 3. 添加全局监听(这样即使鼠标移出元素也能继续跟踪) document.addEventListener('mousemove', handleResizeMove) document.addEventListener('mouseup', handleResizeEnd)}

为什么要添加到 document 而不是 element?

这是个很重要的细节。如果只监听元素本身,当用户快速拖动鼠标超出元素范围时,拖拽就会中断。添加到 document 上,就能保证整个拖拽过程的连续性。

2. handleResizeMove(拖拽中)

consthandleResizeMove=(event)=>{if(!state.resizing)return// 1. 计算鼠标移动的偏移量const deltaX = event.clientX - state.startX const deltaY = event.clientY - state.startY // 2. 根据拖拽方向计算新尺寸let newWidth = state.startWidth let newHeight = state.startHeight if(direction.includes('e')){// 向东(右)拖拽 newWidth = Math.max(state.minWidth, state.startWidth + deltaX)}if(direction.includes('s')){// 向南(下)拖拽 newHeight = Math.max(state.minHeight, state.startHeight + deltaY)}// ... 其他方向类似处理// 3. 直接修改 DOM 元素的样式 dialog.style.width =`${newWidth}px` dialog.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.resizing =false state.resizeDirection =''// 2. 移除监听器(非常重要!防止内存泄漏) document.removeEventListener('mousemove', handleResizeMove) document.removeEventListener('mouseup', handleResizeEnd)// 3. 触发结束事件emit('resize-end',{ width, height })}

为什么要移除监听器?

这是一个新手容易犯的错误。如果不移除,组件销毁后监听器还在,就会导致内存泄漏。你可以把它想象成你租了个房子(添加监听器),搬走时(组件销毁)一定要退租(移除监听器),否则房东(浏览器)会继续收你的钱(占用内存)。

第四步:在 UI 层添加拖拽句柄

逻辑写好了,接下来要在 UI 上让用户能够操作。我在 pc.vue 中添加了 8 个方向的拖拽句柄:

<template> <div ref="dialog"> <!-- 原有的弹窗内容 --> <!-- 新增:8 个方向的拖拽句柄 --> <template v-if="resize && !isFull"> <!-- 四个角 --> <div @mousedown="handleResizeStart('nw')"></div> <div @mousedown="handleResizeStart('ne')"></div> <div @mousedown="handleResizeStart('sw')"></div> <div @mousedown="handleResizeStart('se')"></div> <!-- 四个边 --> <div @mousedown="handleResizeStart('n')"></div> <div @mousedown="handleResizeStart('s')"></div> <div @mousedown="handleResizeStart('w')"></div> <div @mousedown="handleResizeStart('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 提供了完整的生命周期钩子,让我们能在合适的时机做清理工作。这是编写高质量组件的基本功。

坑 2:CSS 单位的精度问题

一开始我用 offsetWidth 获取尺寸,发现会有小数位丢失的问题。比如实际宽度是 500.7px,offsetWidth 返回 501。

解决方案是用 getComputedStyle

const computedStyle =getComputedStyle(dialog)const currentWidth =parseFloat(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 可以:

  1. 通过 WebMCP 协议获取 DialogBox 的能力清单
  2. 识别到 resize 方法可用
  3. 自动调用 handleResize 或修改 width/height 属性
  4. 完成用户的指令

这就是 GenUI(生成式 UI)的核心理念:UI 不再是静态的,而是可以根据用户意图动态调整的

3.2 WebMCP:智能体与组件的"翻译官"

通过这次实践,我对 WebMCP(Model 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 方法
  • ✅ 可以修改 widthheight
  • ❌ 不能在全屏状态下缩放(因为有 isFull 限制)

这才是真正的"人机对话"基础。

构建代码如下:

在这里插入图片描述

3.3 Renderless 架构对 AI 友好的秘密

经过这次实战,我发现 Renderless 架构 简直就是为 AI 而生的:

  1. 逻辑纯净:AI 最擅长生成纯函数式的逻辑代码,不需要理解 DOM
  2. 类型完备:TypeScript 类型定义让 AI 生成的代码更准确
  3. 职责单一:逻辑层只管状态和 API,表现层只管渲染,AI 不容易出错
  4. 易于测试:纯函数更容易编写单元测试,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 实践只是个开始。我已经在规划下一步:

  1. 完善 resizable 功能
    • 添加保持宽高比选项
    • 支持拖拽到边缘自动吸附
    • 添加动画过渡效果
  2. 探索 WebMCP 集成
    • 为 DialogBox 编写 MCP Schema
    • 实现 AI 可调用的标准接口
    • 与大模型平台对接测试
  3. 输出最佳实践
    • 写一篇《如何用 AI 高效开发组件》的教程
    • 录制一个完整的实战视频
    • 在团队内部分享经验

5.2 给同行的建议

如果你也想在前端智能化领域有所建树,我的建议是:

  1. 别观望,先动手:找个真实的开源项目,提交你的第一个 PR
  2. 善用 AI 工具:GitHub Copilot、Cursor、通义灵码,选一个顺手的
  3. 深入理解架构:不要满足于会用 API,要搞懂背后的设计思想
  4. 保持开放心态:新技术层出不穷,快速学习才是核心竞争力
  5. 拥抱开源社区:一个人的力量有限,一群人才能走得更远

六、写在最后

前端智能化不是口号,而是正在发生的现实。

从辅助编码到智能体自主执行,这条路可能还需要走 3 年、5 年甚至 10 年。但每一步前进,都需要我们这一代开发者去铺路。

我很庆幸,自己能参与到这场变革中来。用代码为 AI 时代的基础设施添砖加瓦,这本身就是件很酷的事情,不是吗?

与所有在前端智能化路上探索的朋友共勉。


参考资源

项目源码

Read more

Web Streams 的隐性开销与JavaScript 流处理新方案

Web Streams 的隐性开销与JavaScript 流处理新方案

处理视频流时突然卡顿?处理大文件时内存爆表?这些看似奇怪的问题,可能源于 JavaScript 中一个被广泛采用但设计复杂的标准 API——Web Streams。当你的 Node.js 应用突然因为未消费的 body 耗尽连接池,或者处理大文件时内存爆表,你可能已经踩过 Web Streams 的坑了。 问题:Web Streams 的设计缺陷 Web Streams 是 JavaScript 中处理数据流的标准 API,2014-2016 年设计,旨在统一浏览器和服务器的数据流处理。它被用于 fetch()、Node.js、Cloudflare Workers 等场景,成为现代 Web 应用的数据传输基础。WHATWG Streams Standard 文档 定义了这套机制,初衷是让开发者能以统一方式处理实时数据、大文件、

第五届“长城杯”初赛 2025 Web WP 全

第五届“长城杯”初赛 2025 Web WP 全

文曲签学 上来给了一个 寻词器 的页面,然后根据提示 要进入调试模式 查看网页源码, 看到 通过长按Fn即可进入调试模式 进入调试模式 根据页面提示, 输入 #help,查看可以执行的指令 #list 查看笔记列表 (为了方便输入指令,后续就直接在BP中操作了) 看到 提示 flag在根目录下. #about 提示要关注公众号 双写绕过的,目录穿越读取flag #read ....//....//....//....//flag 成功获取flag. EZ_upload 是一道文件上传题目, 网站只有一个文件上传点.任意上传一个文件后显示了源码. 接受 文件后,对文件名做了简单的过滤. 然后把文件保存在了/tmp目录下,并且对文件执行了tar解包的操作. 文件上传,一般就是要写入 webshell. 但是文件都保存在了/tmp下,所以我们要想办法修改文件的保存位置. 题目隐藏知识 ✅ 关键点:tar 解压符号链接时,默认会保留符号链接(

对比测试:OPENWEBUI vs 传统开发效率提升300%

快速体验 1. 打开 InsCode(快马)平台 https://www.inscode.net 2. 输入框内输入如下内容: 构建一个电商产品详情页对比项目:1. 传统手工开发版本 2. OPENWEBUI生成版本。比较指标包括:开发时长、代码行数、性能指标、可维护性。要求两个版本功能完全一致,包含商品展示、规格选择、购物车等功能。使用Kimi-K2模型自动优化生成代码。 1. 点击'项目生成'按钮,等待项目生成完整后预览效果 最近在做一个电商产品详情页的开发,正好有机会对比了一下传统手工开发和OPENWEBUI自动生成的效率差异。结果让我大吃一惊,忍不住想分享一下这个对比测试的过程和发现。 1. 项目背景 电商产品详情页看似简单,但实际开发中要考虑很多细节:商品图片展示、规格选择、价格计算、购物车功能等。传统开发方式下,前端要写大量HTML/CSS/JS代码,

零基础搭建OCR文字检测系统:科哥开发的WebUI一键启动指南

零基础搭建OCR文字检测系统:科哥开发的WebUI一键启动指南 你是不是也遇到过这些场景: * 手里有一堆发票、合同、证件照片,想快速提取上面的文字,却要一张张手动敲? * 截图里的操作说明看不清,放大后更模糊,复制又不支持? * 做电商运营,每天要处理上百张商品图,光是找图中文字就耗掉半天? 别再靠截图+人工抄写了。今天带你用零代码、零配置、零环境依赖的方式,5分钟内跑起一个专业级OCR文字检测系统——它不是调用API,而是本地部署、完全可控、永久免费的WebUI工具,由开发者“科哥”亲手打造,开箱即用。 这不是Demo,不是演示,而是一个真正能放进工作流、每天稳定运行的OCR检测服务。下面我们就从按下第一个命令开始,手把手带你走完全部流程。 1. 为什么选这个镜像:轻量、精准、开箱即用 cv_resnet18_ocr-detection 这个名字听起来有点技术味,但它的设计哲学非常朴素:把OCR检测这件事,做成和打开网页一样简单。 它不像传统OCR方案那样需要装CUDA、编译OpenCV、下载预训练权重、改配置文件…