本文是系列文章的上篇,建议搭配食用
- 上篇(本文) :为什么 Vue 强烈建议用 Setup?除了复用,更是代码组织(设计动机)
- 下篇(实战) :别再写换皮 Options 了!Vue3 Setup 的真正用法是这3步升级(代码实践)
很多文章在讲 Vue Setup 引入了新语法,以及新语法怎么用。
本文想聊聊 Vue 为什么 引入 Setup。
💡 核心观点 :Composition API 的真正价值,不仅仅是逻辑复用,更是一场代码组织方式的进化。
组件里的"寻宝游戏"
先回想一个熟悉的场景。
当你需要修改一个"用户信息模块"的功能时,在 Options API 的组件里,你需要经历什么?
- 去
data里找user状态定义。 - 去
methods里找fetchUser方法实现。 - 去
mounted里找初始化调用。 - 去
watch里找userId变化后的监听逻辑。
对于稍大一些的组件,页面反复滚动,思维上下横跳是开发常态。
html
// ❌ Options API 实现:同一功能被强制拆散
export default {
data() {
return {
// 🔴 用户模块
user: null,
loading: false,
// 🟢 搜索模块
keyword: '',
results: [],
}
},
methods: {
// 🔴 用户模块
async fetchUser() { /* ... */ },
// 🟢 搜索模块
async search() { /* ... */ },
},
watch: {
// 🔴 用户模块(字符串写法更隐式!)
userId: 'fetchUser',
// 🟢 搜索模块
keyword: 'search',
},
mounted() {
// 🔴 用户模块
this.fetchUser()
},
}
这不是你的问题,是范式的问题。
Options API 强制我们按"选项类型"组织代码 (把状态放一起、把方法放一起)。但人类的大脑是按"业务逻辑"思考的(用户模块、搜索模块、表单模块)。
当组件简单时,这种分散无关痛痒。但当组件变大, "框架的分类方式"就成了"代码组织的天花板" 。我们不是在写业务,而是在填框架定义的表格。
理想结构:按"业务能力"聚合
抛开 Vue 的语法限制,我们心中理想的组件结构,应该是按"逻辑关注点(Logical Concerns)"聚合的。
同一个功能的状态、方法、副作用、生命周期,应该物理相邻。
让我们将上面的 Options 功能全部打散,看看这段伪代码,这是我们在没有框架限制时,最自然的写法:
rust
// ✅ 我们理想中的代码结构:按功能块排列
// 🔴 用户模块
状态:{ user, loading }
方法:{ fetchUser }
监听:{ userId -> fetchUser }
生命周期:{ mounted -> fetchUser }
// 🟢 搜索模块
状态:{ keyword, results }
方法:{ search }
监听:{ keyword -> search }
- 代码都写在一起
- 按功能块组织
在这种结构下:
- 内聚性:修改"用户功能",只需关注第一个代码块,无需跳转。
- 可读性:新人接手,看代码块注释就知道有哪些功能模块。
- 可维护性:删除功能,直接删掉一个块,不会残留碎片。
Vue 引入 Setup,就是为了让我们能写出接近这种理想结构的代码。
Setup 与工具函数
Vue 的 setup 函数,就是让你写所有代码的地方。
附带提供的一套工具函数(ref, watch, onMounted 等),能让你按业务逻辑组织代码。
让我们把上面的理想伪代码,翻译成真实的 Vue Setup 写法的代码:
js
export default {
setup(props) {
// 🔴 用户模块
const user = ref(null) // 状态
const loading = ref(false) // 状态
async function fetchUser() { // 方法
/* ... */
}
watch(() => props.userId, fetchUser) // 监听
onMounted(fetchUser) // 生命周期
// 🟢 搜索模块
const keyword = ref('') // 状态
async function search() { // 方法
/* ... */
}
watch(keyword, search) // 监听
return { user, loading, keyword, search }
}
}
我们想要的:
- 内聚性:修改"用户功能",只需关注第一个代码块,无需跳转。
- 可读性:新人接手,看代码块注释就知道有哪些功能模块。
- 可维护性:删除功能,直接删掉一个块,不会残留碎片。
统统达成。
💡 额外收益 : 当代码按业务逻辑聚合后,你会发现: "提取复用"变成了一个自然的水到渠成的动作。
今天写在 setup 里的"用户模块"代码块, 明天只需要包一层函数、定义好输入输出, 就能变成一个可复用的 useUser() 组合函数。
具体怎么提取、有哪些最佳实践,我们下篇实战文章细聊
为什么 mixin 不行?
读到这儿,可能有 Options API 的老用户会问:
如果把用户逻辑写在一个 mixin 里,不也能聚合吗?为什么非要 Setup?
这是一个非常好的问题。确实,Mixin 也能把相关代码组织在一起。
但从 实际使用 的维度看,Mixin 存在致命缺陷。
js
// UserMixin.js
export default {
data() { return { user: null } },
methods: { fetchUser() {} }
}
// Component.vue
export default {
mixins: [UserMixin],
// 问题:this.user 从哪来?IDE 无法提示,阅读时需要跳转文件
mounted() {
this.fetchUser() // 这个方法是哪来的?需要去 Mixin 里找
}
}
Mixin 的三个使用缺陷:
- 来源不透明 :在组件里看不到
user的定义,除非把组件用到的所有 mixin 都看一遍。 - 命名冲突 :如果两个 mixin 都定义了
user,后引入的会覆盖先引入的,且没有任何警告。 - 数据流向模糊:组件依赖了哪些 mixin 属性?无法肉眼识别,只能靠运行时验证。
想要让对象的某个属性(mixins)使用其它对象后(xxxMixin),能提示动态添加出的属性,这在 ts 里是地狱难度。
这也是
mixin极难推广的原因。当然从上面的逻辑也可以看到,好的 mixin 几乎可以无痛转为 hook
总结:为什么强烈建议用 Setup?
回到最初的问题:为什么写 Vue 强烈建议用 Setup?
| 维度 | Options API | Setup |
|---|---|---|
| 🧩 代码组织 | 按"选项类型"分散 | ✅ 按"业务逻辑"聚合 |
| 🔍 可读性 | 需要脑内拼合逻辑 | ✅ 同一功能物理相邻 |
| 🔧 可维护性 | 修改需跨多处跳转 | ✅ 就近修改,无遗漏 |
| 🧪 可复用性 | Mixin 隐式+冲突 | ✅ 函数显式+类型友好 |
| 🚀 可演进性 | 提取复用成本高 | ✅ 聚合后自然可提取 |
核心结论:
Setup 的真正价值,不是"多了一套新语法", 而是把代码组织权还给了开发者。
它允许你按照人类思考业务的方式写代码, 而不是按照框架分类的方式填表格。
Options API 并没有错,它在小型组件、初学者场景下依然清晰。 但当你的组件越来越复杂,强制按类型组织代码就会成为一种技术债务。
Setup 不是为了让代码变复杂, 而是为了当你在面对一个 1000 行的组件时,不至于无从下手。
下一步:别把 Setup 写成 Options
理解了 Setup 的设计动机,不代表能写好 Composition API。
我观察到一个普遍现象: 很多开发者虽然用了 Setup,但只是把 Options 的代码搬运进了 setup 函数:
data→refmethods→functionmounted→onMounted
结果写出了 "换皮版"的组合式代码:
- ❌ 既没享受到"按逻辑聚合"的组织红利
- ❌ 又失去了 Options"结构清晰"的入门友好
有了"自由",不等于会"用好"。
在下一篇实战文章中,我将从一个真实的详情页案例出发,带你:
3 步升级,真正吃透 Composition API
| 步骤 | 升级内容 | 解决什么问题 |
|---|---|---|
| 升级 1 | 在 setup 内按功能模块组织代码 | 告别"大仓库",让同一功能的代码物理相邻 |
| 升级 2 | 拆分 setup,抽成 useXxx 组合函数 |
逻辑复用变得自然,输入输出显式可控 |
| 升级 3 | 从生命周期驱动转向数据驱动 | 用 watch 替代 onMounted,让代码更声明式 |
👉 立即阅读下篇 :别再写换皮 Options 了!Vue3 Setup 的真正用法是这3步升级