Vue 的悖论:从渐进式框架到宇宙级复杂度,我们是否亲手杀死了它的灵魂?
Vue 诞生于 2014 年,彼时 AngularJS 的繁重与 jQuery 的混乱让尤雨溪萌生了一个朴素的念头:做一个“尽可能降低门槛”的框架。于是,“渐进式”成了 Vue 最鲜明的标签——你可以在一个老旧的 jQuery 页面里,用几行代码引入 Vue,仅对某个局部组件进行增强;也可以一步步引入路由、状态管理、构建工具,最终搭建一个企业级应用。这种从容的攀爬路径让 Vue 在众多框架中脱颖而出,甚至被许多开发者视为从“前端小工”到“架构师”的最佳阶梯。
然而,十年过去,当我重新审视 Vue 生态时,却看到了一幕讽刺的图景。Vue 2 时代,我们还在为 v-model 的语法糖欢呼;Vue 3 发布后,我们却必须面对 ref、reactive、computed、watchEffect 的“四重奏”,外加 shallowRef、triggerRef、customRef 等十几个响应式 API。组合式函数(Composables)本是为了复用逻辑,可大量开发者却将其包装成比 Mixin 更难以追踪的隐式依赖。再看官方推荐的 Vite、Pinia、Nuxt、VueUse……每个工具都声称“简单”,组合在一起却形成了一张庞大到令人窒息的知识图谱。我们一边说着“Vue 很容易上手”,一边却需要阅读长达 80 页的迁移指南才能从 Vue 2 跳到 Vue 3。这难道不是一种巨大的悖论吗?
更值得深思的是,“渐进式”的哲学正在被我们自己的工程实践彻底背弃。为了追求极致的响应式性能,Vue 3 采用了 Proxy 替代 Object.defineProperty,这无疑是一次技术飞跃。但随之而来的,是响应式对象的窃听策略变得极其微秒——ref 需要 .value 解包,reactive 会对嵌套对象自动深度代理,而 toRef 和 toRefs 又要求在解构时小心翼翼。许多团队为了规避这些“坑”,索性全项目都用 ref,把 reactive 贬为“具有误导性的糖”。更极端的是,部分开发者开始强制要求每个组件必须用 <script setup>,并把所有业务逻辑抽象到独立的 useXxx 组合式函数中,声称“这是最佳实践”。结果呢?一个原本简单的表单页面,拆成了 6 个 composables、12 个 ref、8 个 computed,外加 3 个 watch。代码行数从 150 行膨胀到 600 行,而可读性却下降到了历史最低点。我们来看一个典型对比——同样的搜索功能,Vue 2 的 Options API 这样写:
export default {
data() { return { keyword: '', results: [], loading: false }; },
methods: { async onSearch() {
this.loading = true;
try { this.results = await fetch(`/api/search?q=${this.keyword}`).then(r => r.json()); }
finally { this.loading = false; }
}},
watch: { keyword: 'onSearch' } // 简单防抖省略
};
而 Vue 3 的 Composition API 版本:
const keyword = ref('');
const results = ref([]);
const loading = ref(false);
let timer = null;

watch(keyword, () => {
clearTimeout(timer);
timer = setTimeout(async () => {
loading.value = true;
try { results.value = await fetch(`/api/search?q=${keyword.value}`).then(r => r.json()); }
finally { loading.value = false; }
}, 300);
});
看似差不多?但如果再叠加组件间的共享状态、SSR 上下文、自定义指令,以及类型体操式的泛型约束,你会发现自己已经陷入了一个需要同时理解 Proxy 语义、生命周期时序和异步竞态的“反向迷宫”。Options API 的“代码块”至少还有固定的格子,而 Composition API 的“自由”则变成了逻辑碎片的地狱。 我并非否定 Composition API 在大型项目中的价值——它的确解决了跨组件逻辑复用和无 this 作用域带来的类型推断难题。但我们必须承认:Vue 团队为了迎合“企业级复杂度”而牺牲了“心智简单性”,这恰恰与 Vue 的初心背道而驰。
更深层的问题在于,我们整个前端社区正在主动“杀死”渐进式。我们不再允许自己在产品中先写一个简单的 HTML 文件,再局部引入 Vue,而是默认必须用 npm create vue@latest 生成带 TypeScript、ESLint、Prettier、Vitest、Storybook 的“脚手架全家桶”。我们嘲笑任何不写单元测试的仓库,抨击没有用 Pinia 管理全局状态的设计,甚至认为不采用服务端渲染就无法实现“专业的前端”。可当一个细粒度极高的框架耦合了如此多的周边工具后,学习曲线已经逼近甚至超过了 React 和 Angular。我曾经在技术交流会上问过几位刚入行的同学:“你们觉得 Vue 最吸引你的地方是什么?”得到的回答不是“渐进式”,而是“教程多、代码规范、大家都用”。这说明新一代开发者已经将 Vue 视为一门“标准语言”,而非一个“可选的增强工具”。他们从未体验过那个“直接在 script 标签里引入 vue.global.js 就能跑起来”的时代,自然也就无法理解渐进式意味着什么——这难道不是一种集权的悲剧吗?
我想提出一个全新的独立观点:Vue 应该重新变得“愚蠢”。这里的“愚蠢”不是贬义,而是指框架应该主动限制自己的表达空间。与其让开发者使用几十种响应式 API,不如只保留 ref 和 watch 两个核心原语,让 reactive 作为高级别名,并放弃让所有对象自动深度响应。同时,官方应大力推广“局部增强”模式——允许 Vue 组件像 Web Component 一样,通过自定义元素(Custom Element)形式嵌入任意页面,而不必依赖 SFC 编译和构建工具。对于状态管理,我建议回归“模块级单例 + 函数”的模式,用 JavaScript 闭包代替全局的 Pinia store,避免在组件与 store 之间强行建立“代理链”。我们需要的不是更强大的工具,而更清晰的边界。就像 Unix 哲学所说:做一件事,并把它做好。Vue 的初衷是让你“不痛苦地完成 UI 开发”,而不是让你“掌握一门星球大战级别的响应式宇宙学”。
诚然,Vue 3 在响应式性能、类型支持、生态整合等方面都取得了卓越的成就,我们没有任何理由倒退。但作为一个成熟的社区,我们应当保持冷静的审视:是我们在使用框架,还是框架在定义我们?当每个 Vue 组件文件都充斥着 defineProps、defineEmits、withDefaults 和 defineExpose 时,它不再是一份简洁的声明,而是一份复杂的契约。当“渐进式”被曲解为“从简单开始,走向无尽复杂”时,那它就失去了最初的意义。我希望未来的 Vue 社区能重新反思“少即是多”的价值。与其在框架层面不断加料,不如在生态层面多做减法——鼓励直接写原生 JS 处理简单逻辑,鼓励用纯函数模块管理状态,鼓励在不需要响应式的地方毅然跳出 Vue。最终,一个真正的渐进式框架,应当允许你以 10 行代码开始,以 100 行代码结束,而不是以 10 行代码开始,被迫用 1000 行代码维持。愿你写下的每一行 Vue,都像清晨的第一缕阳光一样干净而坦荡。