1. 先把Vue3组件这件事重新捋一遍这几年带团队做前端项目组件库更新了一茬又一茬框架也换过几轮但真正让我反复回头研究的还是Vue3的组件体系。原因很简单组件是Vue这个框架里最基础、也最容易被写歪的东西。基础到几乎每个人都会写容易歪到很多人写了三四年还停留在能跑就行的阶段。Vue3组件到底指的是什么说白了它就是把一段视图、一套状态、一组交互逻辑封装成一个独立单元对外暴露清晰的输入输出接口对内维护自己的响应式数据与生命周期。它能做的事包括但不限于拆分页面结构、复用业务逻辑、隔离样式作用域、承载通信协议。解决的问题是让一个大前端项目不至于变成几千行一个文件的泥潭。这篇文章适合谁看如果你刚接触Vue3正在纠结setup到底怎么用、ref和reactive该选哪个那这里的基础拆解能帮你少走弯路如果你已经写过不少组件但总觉得自己的组件复用起来别扭改一处崩一片那后面关于粒度划分、透传属性、通信选型的内容应该能戳到你的痛点。我尽量不堆概念多讲我实际踩过的坑和验证过的写法。Vue3组件这个主题听起来朴素但真正把它写扎实项目质量的差距会非常明显。2. 组件设计思路从能跑到好维护的分水岭2.1 组合式API改变了什么心智Vue2时代写组件大家的思维定式是按选项分块data放数据、methods放方法、computed放计算属性、watch放监听。一个组件的逻辑如果涉及三四个功能点相关代码就被打散到四五个选项里改一个功能得上下翻半天。Vue3的组合式API把这件事彻底翻过来了——它按功能组织代码而不是按选项类型组织。我写一个带搜索、分页、导出的表格组件时搜索相关的ref、方法、watch可以挨在一起分页的挨在一起导出逻辑再单独一块阅读时是一条线走下来的。这个转变的价值不在于语法新旧而在于逻辑聚合。举个生活化的类比Vue2的写法像把厨房里所有刀具放一个抽屉、所有调料放一个柜子、所有锅具放一个架子做饭时来回跑Vue3的写法像按做一道菜把所有要用的东西摆在一个托盘上端起来就能开工。所以我在设计组件时会先问自己这个组件内部有哪几个独立的功能域每个功能域用一个useXxx的抽离思路去组织哪怕最后不抽成单独文件代码分段也要按功能域来。注意组合式API不等于必须用script setup。script setup是语法糖编译期会展开成setup()函数它在性能上有轻微优势更少的样板代码、更好的类型推导但也牺牲了一些灵活性比如无法在运行时动态决定暴露哪些内容。团队里如果有成员对编译产物不熟建议先在中小型组件里过渡使用。2.2 组件粒度划分的实战判断标准组件拆得太粗一个文件上千行改起来心惊胆战拆得太细满屏都是只包了一层div的俄罗斯套娃组件 props一路往下传维护成本反而更高。我判断粒度的标准有三条单一职责、可独立测试、复用频次。单一职责指的是这个组件只对一类变化负责比如用户信息卡片只负责展示用户信息不负责拉取数据可独立测试指的是我能给它喂几组props就验证它的渲染结果复用频次指的是同样的结构在项目里出现两次以上才值得抽。还有一条容易被忽略的状态归属。如果一个子组件需要知道父组件的很多内部状态才能工作那说明这个子组件拆早了或者它的职责边界没划清。我见过一个按钮组件接收了十几个props来控制禁用、加载、图标位置、颜色主题、点击防抖……这种组件表面复用性强实际上是把自己的职责膨胀成了万能按钮最后谁都不敢改。合理的做法是把基础按钮做薄把业务语义比如提交按钮删除按钮放到组合层去封装。2.3 命名与目录约定别小看这一层组件命名我坚持两条文件名用大驼峰或中划线统一一种团队内统一即可别混用组件名至少两个单词。单单词组件名比如Item.vue、Card.vue在大型项目里几乎必然撞名而且和原生HTML标签容易混淆。目录上我一般按基础组件 / 业务组件 / 布局组件三层分基础组件放components/base不依赖任何业务状态业务组件放components/business可以依赖store和接口布局组件放components/layout只负责骨架和插槽。这个分层不是为了好看而是为了约束依赖方向——基础组件绝对不能反向依赖业务组件否则打包和复用都会出问题。3. 核心机制拆解组件内部到底发生了什么3.1 setup的执行时机与响应式建立setup在组件实例创建之前执行这个之前很关键。此时this是undefined你拿不到组件实例也拿不到props的解构响应性解构会丢失响应式。所以我在setup里访问props时要么用props.xxx要么用toRefs(props)转一下。响应式数据的建立也在这个阶段ref和reactive的差异经常被新手问ref适合包装基本类型和需要整体替换的对象访问时要用.valuereactive适合包装对象但整体替换会丢失响应性而且解构同样会丢。我的经验是默认用ref需要聚合一组相关字段且不做整体替换时用reactive。原因是ref的.value虽然写起来多一个字符但它对替换整个值这种操作是安全的而reactive一旦被整体赋值state {...}原来的代理就断了。团队里因为reactive整体替换导致视图不更新的bug我至少处理过五六次全是同一个坑。import { ref, reactive, toRefs } from vue // 推荐ref 承载会被整体替换的数据 const list ref([]) const loading ref(false) // reactive 用于字段多、结构稳定的表单 const form reactive({ name: , age: 0 }) // 解构时务必用 toRefs否则丢失响应式 const { name, age } toRefs(form)3.2 props、emit、slot三件套的正确姿势props是组件对外的输入契约我一贯主张显式声明类型、默认值、校验。Vue3的props声明支持对象写法type、default、required、validator都能写清楚。别嫌麻烦这层校验在多人协作时是免费的文档也是运行时的一道防线。emit是输出事件名建议用中划线update:modelValue这种除外并且在emits选项里显式声明否则会有警告也不利于类型推导。slot是最容易被低估的部分。很多组件的复用性差本质是插槽设计没做好。我设计插槽的原则是能通过插槽让使用方决定的就不要用props硬编码。比如一个卡片组件头部、主体、底部都留具名插槽使用方想放标题放标题想放操作按钮放操作按钮同时提供作用域插槽把内部数据比如当前项、索引暴露出去使用方就能自定义每一行的渲染。script setup const props defineProps({ title: { type: String, default: }, items: { type: Array, default: () [] } }) const emit defineEmits([select]) /script template div classcard headerslot nameheader{{ title }}/slot/header ul li v-for(item, i) in items :keyi clickemit(select, item) !-- 作用域插槽把 item 和索引交给使用方 -- slot nameitem :itemitem :indexi{{ item.label }}/slot /li /ul footerslot namefooter //footer /div /template提示default: () []这种写法不是多此一举。对象和数组类型的props默认值必须用工厂函数返回否则所有组件实例会共享同一个引用一个实例改了其他实例跟着变这个bug极难排查。3.3 生命周期钩子与副作用清理Vue3的生命周期在组合式API里变成了注册式的onMounted、onUnmounted等函数。我特别想强调onUnmounted或onBeforeUnmount里的清理工作定时器、事件监听、WebSocket连接、第三方实例比如图表实例、地图实例都要在卸载时销毁。我接手过一个项目页面切换十几次后明显卡顿最后定位到一个组件里setInterval没清理十几个定时器在后台跑。还有一个细节onMounted在服务端渲染时不会执行如果组件需要兼容SSR访问window、document的代码要放到onMounted里或者做好环境判断。另外watch和watchEffect返回的停止函数在组件卸载时会自动停止如果在setup同步作用域内创建但如果你是异步创建的watch记得手动调用停止函数。4. 手写一个可复用的业务组件完整实操4.1 需求拆解与接口设计假设我们要做一个带搜索和分页的用户选择器组件用在表单里选人。先把需求拆开对外需要接收已选中的用户、是否多选、每页条数对内需要维护搜索关键词、当前页、列表数据、加载状态、选中集合。接口设计上我倾向用v-model承载选中值Vue3里默认是modelValueupdate:modelValue这样使用方写起来最自然。设计接口时我会先在纸上把这个组件当黑盒用一遍使用方需要传什么、需要监听什么、需要拿到什么内部状态来自定义渲染。这一步做完props和emits的清单基本就出来了。不要一边写实现一边加接口那样最后接口一定是围绕实现长的而不是围绕使用场景长的。4.2 核心代码实现与逐段说明script setup import { ref, watch, computed, onMounted, onUnmounted } from vue const props defineProps({ modelValue: { type: [Array, Object], default: null }, multiple: { type: Boolean, default: false }, pageSize: { type: Number, default: 10 } }) const emit defineEmits([update:modelValue, change]) const keyword ref() const page ref(1) const total ref(0) const list ref([]) const loading ref(false) const selected ref(props.multiple ? [] : null) // 已选中的 id 集合用于列表里高亮 const selectedIds computed(() { if (props.multiple) return new Set((selected.value || []).map(u u.id)) return new Set(selected.value ? [selected.value.id] : []) }) let timer null // 搜索输入防抖避免每敲一个字就请求 watch(keyword, () { clearTimeout(timer) timer setTimeout(() { page.value 1 fetchList() }, 300) }) watch(page, fetchList) async function fetchList() { loading.value true try { // 这里换成真实接口参数按后端约定传 const res await mockRequest({ keyword: keyword.value, page: page.value, size: props.pageSize }) list.value res.rows total.value res.total } finally { loading.value false } } function toggle(user) { if (props.multiple) { const idx selected.value.findIndex(u u.id user.id) idx -1 ? selected.value.splice(idx, 1) : selected.value.push(user) } else { selected.value selected.value?.id user.id ? null : user } emit(update:modelValue, selected.value) emit(change, selected.value) } onMounted(fetchList) onUnmounted(() clearTimeout(timer)) /script这段代码里有几个我特意处理的点。第一keyword的watch用了防抖300毫秒是实测下来比较舒服的值太短请求太密太长用户觉得卡。第二selected用ref而不是直接改props.modelValue遵循单向数据流改完通过emit通知父级。第三selectedIds用computed生成Set列表渲染时高亮判断是O(1)比每次都some遍历一遍快得多。第四onUnmounted清理定时器前面讲过的坑这里就落实了。4.3 样式隔离与透传属性的处理样式隔离我一般用style scoped但它不是万能的。scoped会给元素加>
企业数字化 ERP 产品动态
相关推荐
拆解 md-editor-v3 的 Markdown 渲染预处理链路 做 Markdown 编辑器这块业务的人,几乎都遇到过同一个场景:编辑区里明明写得规规矩矩,一切换到预览就变了样:图片全是裂的、Mermaid 图变成一块灰底文字、目录导航少了一级标题、复制按钮点下去粘贴出来是一堆<span class"… · 2026/11/2 20:09:35
TypeSafe Agent新范式:Jev如何用类型契约解决AI输出不可控难题 1. 从“不说话”的Jev刷屏说起:一个反直觉的AI产品现象最近技术圈里有个挺有意思的现象:一个叫Jev的AI产品在几乎不做营销的情况下,靠用户自发传播刷了屏。更反直觉的是,它主打的卖点居然是“不说话”——或者说,它不像… · 2026/11/2 20:08:34
C++炸弹人源码拆解:游戏循环、格子碰撞与资源加载 简介:这是一套面向C/C学习者和游戏开发入门者的泡泡堂(炸弹人)2D小游戏完整源码,适合用作课程设计、编程实训或自学练手项目。压缩包共346个文件,约75.03MB,其中1个核心cpp源码与cbp工程文件构成主体&#… · 2026/11/2 20:08:34
从零学AI工程:数据、部署、监控的避坑指南与实战路线 从零开始学AI工程:我踩过的坑,和真正值得走的路线“ai engineering from scratch”这个话题,我其实一开始是带着怀疑的——两年前市面上讲AI的课程,十个里面有九个都在教你怎么调sklearn、怎么跑通一个pytorch的官方demoÿ… · 2026/11/2 20:57:01
机器人学习数据格式转换工具lerobot_convertor详解 1. 项目概述 lerobot_convertor 是一个专为机器人学习领域设计的轻量级数据格式转换框架,它的核心功能是将各类机器人数据集统一转换为 LeRobot 兼容格式。这个工具解决了多源数据标准化处理的痛点,特别适合需要快速整合不同来源数据集进行模型训练的研究… · 2026/11/2 20:57:01
能源行业零信任与纵深防御融合架构实践 1. 能源行业安全架构的现状与挑战能源行业作为国家关键基础设施的核心组成部分,其信息系统安全直接关系到国计民生。近年来,随着数字化转型的深入,能源企业面临着前所未有的安全挑战。传统基于边界防护的安全架构在面对新型威胁时显得力不从心… · 2026/11/2 20:57:01
从零构建AI工程体系:Python/TS/Rust/Julia四语言协同实践 1. 为什么“从零构建AI工程体系”不是一句空话,而是当前最真实的生存需求“ai-engineering-from-scratch”这个标题乍看像一门课程名,或是某本技术书的副标题。但如果你最近半年深度参与过至少一个落地AI项目——无论是给内部业务部门搭一个能跑通的RAG知… · 2026/11/2 20:57:01
Doma框架常见问题排查:编译期SQL校验与工程实践 接手一个用Doma框架维护的存量项目时,最先撞上的往往不是业务逻辑,而是满屏的编译期报错:SQL文件找不到、绑定变量对不上、生成的DAO实现类飘红。Doma这套Java数据库访问框架的思路很特别,它把SQL从Java代码里彻底抽出来放进独立的… · 2026/11/2 20:57:01
VGG19图像分类迁移学习:冻结策略与验收避坑指南 简介:面向智能计算系统课程实验与深度学习部署入门者,这份实验4-1资料围绕VGG19图像分类任务,提供从卷积网络推理到硬件加速评估的实践素材,适合需要理解模型量化与不同计算平台性能差异的学习者。压缩包共4个文件,由3… · 2026/11/2 20:56:00
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18
雷达信号调制技术解析:从LFM到伪线性调频原理与应用 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
基于MAX14431与TM4C123的工业SPI隔离电路设计全攻略 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
PLFM_RADAR:相位线性调频雷达信号处理框架 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18