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 的缓存机制很聪明,只有真正依赖变化时才会重新计算。嗯,放心用吧。

小技巧: 计算属性默认是只读的。如果你需要修改它,可以提供一个 setter:
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 的大门就打开了。下一章咱们会深入组件通信,那才是真正好玩的地方。到时候见!