“React 和 Angular 打架的时候,Vue 悄悄偷走了开发者的心。”
林小凡第一次听说 Vue,是在某个深夜的技术论坛上。
当时他正深陷 React 的 useEffect
依赖数组地狱,而隔壁工位的同事却哼着小曲,愉快地敲着键盘,屏幕上的代码简洁得像一首诗:
html
<template>
<div>
<h1>{{ message }}</h1>
<button @click="reverseMessage">翻转</button>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello Vue!'
}
},
methods: {
reverseMessage() {
this.message = this.message.split('').reverse().join('')
}
}
}
</script>
<style scoped>
h1 {
color: #42b983;
}
</style>
“这……这也太简单了吧?!”林小凡瞪大眼睛,仿佛看到了前端世界的乌托邦。
第一节:渐进式的诱惑
Vue 的宣传语是**“渐进式框架”**,而林小凡很快明白了它的含义。
他接手的第一个 Vue 项目,竟然是从一个 jQuery 老系统逐步迁移过来的。
原来的代码:
html
<!-- 旧版jQuery页面 -->
<div id="app">
<div class="header"></div>
<div class="content">
<!-- 一堆DOM操作 -->
</div>
</div>
<script src="jquery.min.js"></script>
<script src="legacy.js"></script>
迁移的第一步,仅仅是在页面底部加了一行:
html
<!-- 引入Vue -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
然后,在某个角落慢慢替换:
javascript
// 渐进式改造
new Vue({
el: '#header',
data: {
title: '新版Vue头部'
},
template: `<div>{{ title }}</div>`
})
“居然不用重写整个项目?!”林小凡震惊了,“React 可是要求全盘推翻啊!”
墨尘发来一个微笑表情:
“这就是 Vue 的温柔陷阱——它从不强迫你革命,而是邀请你改良。”
第二节:单文件组件的优雅
当林小凡第一次打开 .vue
文件时,他仿佛听到了天使的合唱。
html
<template>
<!-- HTML模板 -->
</template>
<script>
// JavaScript逻辑
</script>
<style scoped>
/* 只作用于本组件的CSS */
</style>
对比 React 的 JSX + CSS-in-JS 方案:
jsx
function Component() {
const [state, setState] = useState();
return (
<div css={/* 样式对象 */}>
{/* 逻辑和UI混合 */}
</div>
);
}
“这才是我想要的关注点分离!”林小凡热泪盈眶。
直到他尝试在 Vue 里写一个复杂逻辑:
html
<script>
export default {
data() {
return { count: 0 }
},
computed: {
doubleCount() {
return this.count * 2
}
},
watch: {
count(newVal) {
console.log(`计数变化:${newVal}`)
}
},
methods: {
increment() {
this.count++
}
},
created() {
console.log('组件诞生!')
}
}
</script>
“等等……data
要写成函数返回对象?methods
和 computed
为什么要分开?”
墨尘的回复一针见血:
“Vue 的约定大于配置——听话就有糖吃,叛逆就吃警告。”
第三节:响应式的魔法与诅咒
Vue 最让林小凡着迷的是它的响应式系统。
简单到令人发指的数据驱动:
javascript
data() {
return {
user: {
name: '林小凡',
skills: ['JS', 'CSS']
}
}
}
然后在模板里:
html
<p>{{ user.name }}</p>
<ul>
<li v-for="skill in user.skills" :key="skill">{{ skill }}</li>
</ul>
修改数据时,UI 自动更新:
javascript
this.user.name = '大凡' // 视图同步变化
this.user.skills.push('Vue') // 数组也能响应!
“这才叫双向绑定!Angular 那个$scope
太丑了!”林小凡兴奋地大喊。
直到他遇到这个情况:
javascript
this.user.skills[0] = 'JavaScript' // 视图不更新!
this.user.age = 25 // 新属性不响应!
解决方案是:
javascript
this.$set(this.user.skills, 0, 'JavaScript') // 特殊API
this.$set(this.user, 'age', 25) // 还是特殊API
“说好的‘渐进式’呢?!”林小凡摔键盘,“这明明是‘渐进式坑爹’!”
第四节:Vuex 的状态迷宫
当项目复杂度上升,林小凡不得不引入 Vuex。
对比 Redux 的繁琐,Vuex 看起来简单多了:
javascript
// store.js
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
asyncIncrement({ commit }) {
setTimeout(() => commit('increment'), 1000)
}
},
getters: {
doubleCount: state => state.count * 2
}
})
组件中使用:
javascript
computed: {
...mapState(['count']),
...mapGetters(['doubleCount'])
},
methods: {
...mapMutations(['increment']),
...mapActions(['asyncIncrement'])
}
“这语法糖甜到掉牙了!”林小凡刚赞叹完,就发现项目里出现了:
this.$store.state.moduleA.moduleB.data
- 嵌套五层的
namespaced
模块 - 同时存在
mutations
和actions
的异步地狱
墨尘发来一个悲伤的表情:
“所有状态管理方案,最终都会变成你看不懂的样子。”
第五节:Vue 3 的颠覆
当林小凡刚熟悉 Vue 2,Vue 3 带着 Composition API 杀到了:
html
<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 吗?!”林小凡三观碎裂。
更冲击的是,Vue 3 的响应式系统完全重写:
javascript
import { reactive } from 'vue'
const obj = reactive({ a: 1 })
obj.a = 2 // 现在连数组索引和新增属性都响应了!
“所以 Vue 2 的$set
是历史包袱?”他忽然理解了技术演进的残酷。
终节:温柔的代价
某个深夜,林小凡在 GitHub 上看到尤雨溪早期的一段采访:
“Vue 的设计理念是降低前端开发的门槛。”
他看了看自己写的 Vue 代码,又看了看旁边 React 项目的useMemo
依赖数组,忽然明白了 Vue 的“温柔陷阱”——
它用最亲切的方式带你进门,但当你想攀登高峰时,会发现:
所有框架,最终都会同样复杂。
窗外,月光照在并排打开的三个文档上:
- Vue 2 Options API
- Vue 3 Composition API
- React Hooks
像三个时代的墓碑,又像三扇通向未来的门。
(第十章 完)
下一章预告:
第十一章:框架选型的十字路口——技术栈的抉择
“当技术Leader说‘我们选型要考虑未来’时,真正的意思是‘我昨天刚看了这个框架的发布会’。”——某被迫迁移三次的技术团队