注:本观点不代表号主观点,以下内容来源网友投稿
作为一个曾经的Vue死忠粉,我必须承认一个残酷的事实:我被Vue的"渐进式"营销话术整整骗了3年。
2021年,我刚入行时就选择了Vue,理由很简单——官网说它"渐进式",学习曲线平缓,对新手友好。那时的我深信不疑,甚至在技术群里为Vue辩护,嘲笑那些"装逼"选择React的同事。
直到2024年,公司强制要求转React,我才真正明白什么叫**"温水煮青蛙"**。
今天,我要把这3年的血泪教训全部说出来。
第一年:被"易学"假象迷惑的蜜月期
Vue的甜蜜陷阱
还记得第一次写Vue代码时的兴奋:
<template>
<div>
<h1>{{ title }}</h1>
<button @click="count++">点击了 {{ count }} 次</button>
</div>
</template>
<script>
export default {
data() {
return {
title: 'Hello Vue!',
count: 0
}
}
}
</script>
太简单了! 模板就是HTML,逻辑就是普通的JavaScript对象。我当时想:这就是传说中的"渐进式"框架?果然比React那堆JSX语法糖要人性化多了。
第一次觉得不对劲
项目做了半年后,我开始遇到一些奇怪的问题:
<!-- 这个看似简单的列表,竟然有性能问题 -->
<template>
<div>
<div v-for="item in expensiveList" :key="item.id">
{{ computeExpensiveValue(item) }}
</div>
</div>
</template>
<script>
export default {
methods: {
// 这个方法在每次渲染时都会被调用!
computeExpensiveValue(item) {
console.log('重复计算了!'); // 疯狂打印
return item.value * Math.random();
}
}
}
</script>
我花了整整一周才搞明白要用computed
。但问题是,Vue的文档从来没有强调过这种性能陷阱的严重性。
这就是"渐进式"的第一个谎言:它让你以为简单就是好的,却不告诉你简单背后的复杂性。
第二年:深入Vue生态的痛苦觉醒
Vuex:状态管理的噩梦开始
当项目复杂度上升,我开始接触Vuex:
// Vuex的冗余写法让我怀疑人生
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
INCREMENT(state) {
state.count++
}
},
actions: {
increment({ commit }) {
commit('INCREMENT')
}
},
getters: {
doubleCount: state => state.count * 2
}
})
// 在组件里使用更是灾难
exportdefault {
computed: {
...mapState(['count']),
...mapGetters(['doubleCount'])
},
methods: {
...mapActions(['increment'])
}
}
为了改变一个简单的数字,我需要写4个不同的函数! 这就是传说中的"渐进式"?
同期我偷偷看了React的状态管理:
// React Hook:简洁到令人发指
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>
点击
</button>
</div>
);
}
我开始怀疑Vue团队是不是故意把简单的事情复杂化。
模板语法的表达力天花板
Vue的模板看起来很直观,但当你需要复杂逻辑时:
<template>
<div>
<!-- 这种嵌套看起来就很蠢 -->
<div v-if="user.isLoggedIn">
<div v-if="user.hasPermission">
<div v-if="!user.isBlocked">
<div v-for="item in user.items" :key="item.id">
<span v-if="item.isVisible">
{{ item.title | truncate(20) | capitalize }}
</span>
</div>
</div>
<div v-else>
用户被封禁
</div>
</div>
<div v-else>
权限不足
</div>
</div>
<login-form v-else />
</div>
</template>
对比React的JSX:
function UserDashboard({ user }) {
if (!user.isLoggedIn) return<LoginForm />;
if (!user.hasPermission) return<div>权限不足</div>;
if (user.isBlocked) return<div>用户被封禁</div>;
return (
<div>
{user.items
.filter(item => item.isVisible)
.map(item => (
<span key={item.id}>
{capitalize(truncate(item.title, 20))}
</span>
))}
</div>
);
}
JSX就是JavaScript,我可以用任何编程技巧。而Vue的模板就是模板,限制死了。
这时我意识到:"渐进式"的第二个谎言是让你以为限制就是保护,实际上是把你困在了一个笼子里。
第三年:Vue 3的"革命"暴露了什么?
Composition API:向React的妥协
Vue 3推出了Composition API,官方说这是"更好的逻辑复用":
<script setup>
import { ref, computed, onMounted } from 'vue'
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
onMounted(() => {
console.log('组件挂载了')
})
</script>
等等...这不就是React Hooks的翻版吗?
// React Hook(2018年就有了)
function useCounter() {
const [count, setCount] = useState(0);
const doubleCount = useMemo(() => count * 2, [count]);
const increment = useCallback(() => {
setCount(c => c + 1);
}, []);
useEffect(() => {
console.log('组件挂载了');
}, []);
return { count, doubleCount, increment };
}
Vue团队用了整整2年时间,才"发明"出React在2018年就有的解决方案。
这就是"渐进式"的第三个谎言:它不是创新,而是缓慢的跟随。
转向React的痛苦与顿悟
第一周:语法冲击
// 刚开始看到这种写法我是拒绝的
function TodoApp() {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState('all');
const filteredTodos = useMemo(() => {
return todos.filter(todo => {
if (filter === 'completed') return todo.completed;
if (filter === 'active') return !todo.completed;
returntrue;
});
}, [todos, filter]);
return (
<div className="todo-app">
{filteredTodos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={() => toggleTodo(todo.id)}
onDelete={() => deleteTodo(todo.id)}
/>
))}
</div>
);
}
第一反应:这什么鬼语法?HTML和JavaScript混在一起,太丑了!
第一个月:思维转换
但是一个月后,我开始理解React的哲学:
一切都是JavaScript。 没有特殊的模板语法,没有魔法指令,就是纯粹的函数调用。
// 这种代码在Vue里根本写不出来
function DataTable({ data, columns, filters }) {
const processedData = useMemo(() => {
return data
.filter(row =>
Object.entries(filters).every(([key, value]) =>
row[key].includes(value)
)
)
.sort((a, b) => sortComparator(a, b))
.slice(pagination.start, pagination.end);
}, [data, filters, sortConfig, pagination]);
return (
<table>
<thead>
{columns.map(col => (
<th key={col.key} onClick={() => handleSort(col.key)}>
{col.title}
{sortConfig.key === col.key && (
<SortIcon direction={sortConfig.direction} />
)}
</th>
))}
</thead>
<tbody>
{processedData.map(row => (
<TableRow key={row.id} data={row} columns={columns} />
))}
</tbody>
</table>
);
}
在React里,我可以用JavaScript的全部能力。在Vue里,我只能用模板语法的那点可怜功能。
第三个月:生态震撼
React的生态系统让我真正感受到了什么叫"降维打击":
// Next.js:服务端渲染轻松搞定
exportasyncfunction getServerSideProps() {
const data = await fetchData();
return { props: { data } };
}
// React Query:接口状态管理的神器
function UserProfile({ userId }) {
const { data, isLoading, error } = useQuery(
['user', userId],
() => fetchUser(userId),
{ staleTime: 5 * 60 * 1000 }
);
if (isLoading) return<Skeleton />;
if (error) return<ErrorBoundary error={error} />;
return<ProfileCard user={data} />;
}
// React Hook Form:表单处理不再痛苦
function LoginForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email', { required: true })} />
{errors.email && <span>邮箱必填</span>}
</form>
);
}
Vue的生态就像乡村小路,React的生态就像高速公路网。
第六个月:终极顿悟
性能对比的残酷现实
我做了一个对比实验,同样的复杂列表组件:
// Vue版本:优化后的代码
<template>
<virtual-list
:data-sources="list"
:data-key="'id'"
:data-component="itemComponent"
:keeps="30"
:extra-props="{ onDelete: handleDelete }"
/>
</template>
// React版本:开箱即用
function VirtualizedList({ items }) {
return (
<VariableSizeList
height={600}
itemCount={items.length}
itemSize={getItemSize}
itemData={items}
>
{({ index, style, data }) => (
<ListItem
style={style}
item={data[index]}
onDelete={handleDelete}
/>
)}
</VariableSizeList>
);
}
性能测试结果:
Vue版本:1000条数据,首次渲染380ms
React版本:1000条数据,首次渲染180ms
相同功能,React版本代码更少,性能更好,生态更丰富。
招聘市场的血腥真相
我去看了看招聘网站:
北京前端岗位统计(2024年数据):
React相关:2847个职位
Vue相关:1203个职位
Angular相关:345个职位
薪资对比:
React高级:25-40K
Vue高级:20-32K
全栈(React):30-50K
这就是市场给出的最残酷答案:Vue的"渐进式"只是让你在一个越来越小的池子里游泳。
痛苦反思:被"渐进式"骗了什么?
谎言一:入门容易就是好事
Vue确实容易入门,但这种"容易"是有代价的:
<!-- Vue:看起来简单,实际上隐藏了复杂性 -->
<div v-for="item in list" :key="item.id">
{{ item.name }}
</div>
<!-- 你不知道的事实:
1. Vue在背后做了大量的proxy劫持
2. 依赖收集系统比React的diff算法更复杂
3. 模板编译器做了你不知道的优化和限制
-->
{/* React:看起来复杂,但逻辑透明 */}
{list.map(item => (
<div key={item.id}>
{item.name}
</div>
))}
{/* 你知道的事实:
1. 这就是一个map函数调用
2. 返回的是virtual DOM对象
3. 没有魔法,没有隐藏逻辑
*/}
"渐进式"让你以为自己在学习,实际上只是在使用别人包装好的黑盒。
谎言二:模板比JSX更直观
Vue的模板语法看起来像HTML,但这种"像"是欺骗性的:
<!-- Vue:伪HTML,实际上有很多特殊规则 -->
<template>
<div>
<!-- v-if和v-for不能在同一个元素上 -->
<!-- :key必须是唯一值 -->
<!-- @click.stop.prevent这种修饰符语法 -->
<!-- {{ }}表达式有作用域限制 -->
<div
v-for="item in list"
:key="item.id"
v-if="item.visible"
@click.stop="handleClick(item, $event)"
>
{{ item.title | filter1 | filter2 }}
</div>
</div>
</template>
{/* React:就是JavaScript,没有伪装 */}
<div>
{list
.filter(item => item.visible)
.map(item => (
<div
key={item.id}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
handleClick(item, e);
}}
>
{filter2(filter1(item.title))}
</div>
))}
</div>
Vue的模板让你以为在写HTML,实际上在学习一门新的DSL。React的JSX让你知道在写JavaScript。
谎言三:渐进式就是灵活性
Vue声称可以"渐进式"使用,从简单的HTML增强到复杂的SPA。但现实是:
// Vue的"渐进式"陷阱
// 第一阶段:简单使用
new Vue({
el: '#app',
data: { message: 'Hello' }
});
// 第二阶段:组件化
Vue.component('my-component', { ... });
// 第三阶段:单文件组件
// 突然需要webpack、vue-loader、babel...
// 第四阶段:Vuex
// 突然需要学习mutations、actions、modules...
// 第五阶段:Vue Router
// 突然需要理解守卫、懒加载、嵌套路由...
每个"渐进"都是一个新的学习成本,最终的复杂度并不比React低。
而React是诚实的:
// React:从一开始就告诉你要学什么
function App() {
return <div>Hello World</div>;
}
// 需要状态?useState
// 需要副作用?useEffect
// 需要路由?React Router
// 需要状态管理?Context + useReducer
// 所有概念都是JavaScript概念的延伸
转React后的残酷对比
开发效率对比
相同功能的代码量对比:
<!-- Vue:表单处理 -->
<template>
<form @submit.prevent="onSubmit">
<div class="form-group">
<label>邮箱</label>
<input
v-model="form.email"
type="email"
:class="{ 'error': errors.email }"
@blur="validateEmail"
/>
<span v-if="errors.email" class="error-msg">
{{ errors.email }}
</span>
</div>
<div class="form-group">
<label>密码</label>
<input
v-model="form.password"
type="password"
:class="{ 'error': errors.password }"
@blur="validatePassword"
/>
<span v-if="errors.password" class="error-msg">
{{ errors.password }}
</span>
</div>
<button type="submit" :disabled="!isValid">
提交
</button>
</form>
</template>
<script>
export default {
data() {
return {
form: {
email: '',
password: ''
},
errors: {},
touched: {}
}
},
computed: {
isValid() {
return Object.keys(this.errors).length === 0 &&
this.form.email &&
this.form.password;
}
},
methods: {
validateEmail() {
this.touched.email = true;
if (!this.form.email) {
this.$set(this.errors, 'email', '邮箱必填');
} else if (!/\S+@\S+\.\S+/.test(this.form.email)) {
this.$set(this.errors, 'email', '邮箱格式错误');
} else {
this.$delete(this.errors, 'email');
}
},
validatePassword() {
this.touched.password = true;
if (!this.form.password) {
this.$set(this.errors, 'password', '密码必填');
} else if (this.form.password.length < 6) {
this.$set(this.errors, 'password', '密码至少6位');
} else {
this.$delete(this.errors, 'password');
}
},
onSubmit() {
this.validateEmail();
this.validatePassword();
if (this.isValid) {
// 提交逻辑
console.log('提交', this.form);
}
}
}
}
</script>
// React:相同功能,一半代码
function LoginForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) => {
console.log('提交', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="form-group">
<label>邮箱</label>
<input
{...register('email', {
required: '邮箱必填',
pattern: {
value: /\S+@\S+\.\S+/,
message: '邮箱格式错误'
}
})}
type="email"
className={errors.email ? 'error' : ''}
/>
{errors.email && (
<span className="error-msg">
{errors.email.message}
</span>
)}
</div>
<div className="form-group">
<label>密码</label>
<input
{...register('password', {
required: '密码必填',
minLength: {
value: 6,
message: '密码至少6位'
}
})}
type="password"
className={errors.password ? 'error' : ''}
/>
{errors.password && (
<span className="error-msg">
{errors.password.message}
</span>
)}
</div>
<button type="submit">提交</button>
</form>
);
}
Vue版本:80行代码,React版本:40行代码。而且React版本功能更强大,性能更好。
学习成本的真实对比
Vue的学习路径(被包装的复杂性):
模板语法(v-if, v-for, v-model, @click等)
组件通信(props, emit, slot)
响应式原理(data, computed, watch)
生命周期(created, mounted, updated等)
Vuex(state, mutations, actions, getters)
Vue Router(routes, guards, params)
Composition API(ref, reactive, computed, watch)
React的学习路径(透明的复杂性):
JSX(就是JavaScript表达式)
组件(就是函数)
State(useState hook)
副作用(useEffect hook)
状态管理(Context + useReducer,或第三方库)
路由(React Router,概念简单)
Vue看起来概念少,实际上每个概念都有很多隐藏的细节。React概念透明,学会了就是真的会了。
最终觉悟:什么是真正的"渐进式"?
经过3年的痛苦转换,我终于明白了什么是真正的"渐进式":
Vue的伪渐进式
// Vue:表面渐进,实际断层
// 阶段1:看起来很简单
new Vue({ data: { count: 0 } });
// 阶段2:突然复杂化
// 需要理解响应式、虚拟DOM、模板编译...
// 阶段3:复杂度爆炸
// Vuex、Router、SSR、TypeScript支持...
React的真渐进式
// React:概念一致性
// 阶段1:函数组件
function Hello() { return<div>Hello</div>; }
// 阶段2:加状态
function Counter() {
const [count, setCount] = useState(0);
return<div onClick={() => setCount(count + 1)}>{count}</div>;
}
// 阶段3:加副作用
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []);
return<div>{data}</div>;
}
// 所有复杂功能都是这些基础概念的组合
React的每个概念都是前一个概念的自然延伸,Vue的每个阶段都需要重新学习新的范式。
血泪总结:给还在Vue坑里的同学
1. 就业市场不会等你
招聘需求对比(2024年实际数据):
- React: 占前端岗位的65%
- Vue: 占前端岗位的28%
- Angular: 占前端岗位的7%
薪资天花板:
- React高级:最高可达50K
- Vue高级:最高35K左右
市场已经用脚投票了。
2. 技术栈选择的马太效应
一旦选择了小众技术栈,你会发现:
学习资源少
社区活跃度低
第三方库质量参差不齐
解决问题只能靠自己
而React生态系统的繁荣是Vue永远追不上的。
3. Vue 3已经承认了什么?
Vue 3推出Composition API,实际上是在承认:
Vue 2的Options API有局限性
React Hooks的设计更优秀
模板语法的表达力不足
连Vue官方都在向React学习,你还在坚持什么?
4. 不要被沉没成本绑架
很多人不愿意转React,理由是"我已经学会Vue了"。但是:
// 这种想法很危险
if (已投入时间 > 转换成本) {
继续使用Vue; // 错误的决策
} else {
转向React; // 正确但痛苦的决策
}
// 正确的思考方式
if (未来收益(React) > 未来收益(Vue)) {
立即转向React; // 唯一正确的决策
}
沉没成本已经沉没了,重要的是未来。
最后的忠告
如果你是新人,直接学React,不要走我的弯路。
如果你已经在用Vue,越早转React越好,时间成本只会越来越高。
如果你是技术负责人,为了团队的未来,请认真考虑技术栈迁移。
Vue的"渐进式"是一个美丽的营销词汇,但在技术的世界里,没有免费的午餐。你以为的"容易",只是把复杂性延后了而已。
而React从一开始就告诉你真相:前端开发本来就是复杂的,与其自欺欺人,不如直面现实。
写在最后:这篇文章可能会得罪很多Vue开发者,但作为一个过来人,我有义务说出真话。技术选择没有对错,但有优劣。希望每个开发者都能做出最有利于自己职业发展的选择。
💬 **你是否也有类似的技术栈迁移经历?在评论区分享你的故事,让更多人避免走弯路。记得点赞支持,让更多人看到这篇良心文章
注:本观点不代表号主观点