Vue3 核心概念:从零搭建你的第一个响应式应用
好,咱们今天来聊聊 Vue3 的核心概念。说实话,这部分内容我每次带新人时都会反复讲。因为这是地基,地基打不牢,后面盖楼肯定歪。
我个人习惯把 Vue3 的核心拆成五个部分:创建应用、模板语法、响应式数据、计算属性、侦听器。咱们一个一个来。
1. 创建 Vue3 应用:从 createApp 开始
在 Vue2 时代,我们用的是 new Vue()。到了 Vue3,换成了 createApp。这不仅仅是 API 的变化,更是架构的升级。
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
嗯,这里要注意:createApp 返回的是一个应用实例。你可以挂载多个应用,每个应用都有自己的上下文。我在项目中遇到过一个问题——两个 Vue 应用同时存在时,全局组件注册会互相影响。后来发现,用 createApp 创建的应用实例是隔离的,完美解决了这个问题。
mount() 之前调用。比如 app.use(router)、app.component('MyComponent', MyComponent)。
2. 模板语法:花括号与指令的魔法
模板语法说白了就是「数据驱动视图」。你写一个 {{ message }},Vue 就会自动把 message 的值渲染到页面上。
<template>
<div>
<p>{{ message }}</p>
<p v-text="message"></p>
<p v-html="rawHtml"></p>
</div>
</template>
<script setup>
const message = 'Hello Vue3!'
const rawHtml = '<span style="color: red">红色文字</span>'
</script>
你想想看,v-text 和 {{ }} 效果一样,但 v-html 会解析 HTML。我曾经在项目中直接用 v-html 渲染用户输入的内容,结果被 XSS 攻击了。嗯,从那以后我再也不敢乱用 v-html 了。如果你必须用,记得先做安全过滤。
v-bind 导致属性绑定失败。比如 <img src="{{ url }}"> 是不行的,必须写成 <img :src="url">。记住,: 是 v-bind: 的简写。
3. 响应式数据:ref 与 reactive 的选择
这是 Vue3 最核心的变化。Vue2 用的是 Object.defineProperty,Vue3 换成了 Proxy。性能更好,而且能监听数组和新增属性。
我个人习惯这样区分:
- ref:用于基本类型(string、number、boolean),也用于对象。但访问时要用
.value。 - reactive:只用于对象(包括数组、Map、Set)。访问时不需要
.value。
import { ref, reactive } from 'vue'
// ref 示例
const count = ref(0)
console.log(count.value) // 0
count.value++
// reactive 示例
const user = reactive({
name: '张三',
age: 25
})
console.log(user.name) // 张三
user.age++
为什么会有 .value 这个设计?因为 JavaScript 的基本类型是按值传递的,没法直接变成响应式。Vue 用 ref 包装了一层,通过 .value 来触发依赖收集。
我在项目中遇到过一个问题:用 reactive 包裹一个基本类型,结果发现它不响应。后来才意识到,reactive 只能处理对象。如果你非要让一个数字变成响应式,用 ref 就对了。
ref 就用 ref,除非你确定要处理深层嵌套的对象。因为 ref 在模板中会自动解包,不需要写 .value,写起来更舒服。
4. 计算属性:让数据自己算自己
计算属性,说白了就是「依赖其他数据的数据」。它会在依赖变化时自动重新计算,而且有缓存机制。
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
const fullName = computed(() => {
return firstName.value + ' ' + lastName.value
})
console.log(fullName.value) // 张 三
firstName.value = '李'
console.log(fullName.value) // 李 三
你想想看,如果不用计算属性,你每次都要手动拼接 firstName + lastName,多麻烦。而且计算属性会缓存结果,只要依赖不变,就不会重新计算。这在性能上比方法调用好很多。
我记得有一次,我在一个表格里用了 10 个计算属性,每个都依赖同一个数组。当时担心性能问题,后来发现 Vue 的缓存机制很聪明,只有真正依赖变化时才会重新计算。嗯,放心用吧。
const fullName = computed({
get: () => firstName.value + ' ' + lastName.value,
set: (val) => {
const names = val.split(' ')
firstName.value = names[0]
lastName.value = names[1]
}
})
5. 侦听器:当数据变化时,做点什么
侦听器和计算属性不同。计算属性是「根据数据算出新数据」,侦听器是「数据变化时执行副作用」。比如发请求、存 localStorage、操作 DOM。
import { ref, watch } from 'vue'
const keyword = ref('')
watch(keyword, (newVal, oldVal) => {
console.log(`搜索关键词从 "${oldVal}" 变成了 "${newVal}"`)
// 这里可以发请求搜索
})
侦听器有三种写法:
| 写法 | 说明 | 示例 |
|---|---|---|
watch(source, callback) |
监听单个数据源 | watch(count, (newVal) => {}) |
watch([source1, source2], callback) |
监听多个数据源 | watch([a, b], ([newA, newB]) => {}) |
watch(source, callback, { deep: true }) |
深度监听对象内部变化 | watch(user, callback, { deep: true }) |
我曾经在项目中犯过一个错误:用 watch 监听一个对象,但没加 deep: true。结果对象内部属性变了,侦听器没触发。排查了半天才发现是深度监听的问题。嗯,记住:监听对象时,默认只监听引用变化,不监听内部属性变化。
watch 的回调里直接修改了被监听的数据,结果造成了死循环。比如监听 count,然后在回调里 count.value++。Vue 会检测到这种循环,但最好还是避免。如果你确实需要修改,可以用 flush: 'post' 选项。
总结一下
好了,咱们把 Vue3 的核心概念过了一遍。说白了就是:
- createApp:创建应用实例,挂载到 DOM 上。
- 模板语法:用
{{ }}和指令把数据渲染到视图。 - ref 与 reactive:让数据变成响应式,驱动视图更新。
- 计算属性:依赖其他数据,自动计算,有缓存。
- 侦听器:数据变化时执行副作用,比如发请求。
这些概念你掌握了,Vue3 的大门就打开了。下一章咱们会深入组件通信,那才是真正好玩的地方。到时候见!