文章目录
- [一、Vue 整体介绍](#一、Vue 整体介绍)
- [二、Vue3 Hello World 四步流程](#二、Vue3 Hello World 四步流程)
- [三、setup 入口函数](#三、setup 入口函数)
-
- [插值语法(声明式渲染 / 胡子语法)](#插值语法(声明式渲染 / 胡子语法))
- 四、响应式核心概念
- [五、reactive 响应式 API](#五、reactive 响应式 API)
- [六、ref 响应式 API](#六、ref 响应式 API)
- [七、reactive vs ref 选型方案](#七、reactive vs ref 选型方案)
- 全文总结
- 核心知识点复盘
- [常见问题 / 避坑指南](#常见问题 / 避坑指南)
本文基于 Vue3 组合式 API(Composition API),从零开始梳理 Vue3 的核心概念与响应式原理,配合可运行的代码示例,适合前端新手入门及有经验的开发者快速复盘。
一、Vue 整体介绍
Vue (读音 /vjuː/,同 view)是一套用于构建用户界面 的渐进式 JavaScript 框架。拆解这三个关键词,就能理解 Vue 的本质定位:
| 关键词 | 含义 | 通俗理解 |
|---|---|---|
| 构建用户界面 | 基于数据生成 HTML 标签结构,配合 CSS 样式,渲染出用户可见的 UI | Vue 的核心工作就是"把数据变成页面" |
| 渐进式 | 可以循序渐进地学习,学多少用多少,不强求一次性掌握全部 | 就像搭积木,你可以只用一块,也可以搭出城堡 |
| 框架 | 提供一套固定的语法规则,开发时必须遵循 Vue 的既定规范 | 在 Vue 的地盘,就得按 Vue 的规矩来写代码 |
渐进式的两层含义:
① 局部改造 ------ 轻量嵌入现有项目
你有一个老旧的 jQuery 项目,不想全部重写,只想把某个复杂的表单模块改成响应式的。没问题------直接在页面中引入一个 Vue 的 CDN 脚本,就能在局部区域使用 Vue 的功能。
html
<!-- 仅需一行 script 引入,即可在现有项目中局部使用 Vue -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
② 整站开发 ------ 完整的现代化技术栈
当你需要从零搭建一个完整的中大型 Web 应用时,Vue 生态提供了全套解决方案:
Vue3(核心框架)+ VueRouter(路由)+ Pinia(状态管理)+ Vite(工程化构建)+ 模块化开发
重点 :"Vue 的渐进式是什么意思",核心回答就是------可以自底向上逐层应用。简单页面只用核心库,复杂应用再引入路由、状态管理等生态工具,不强制全量使用。
二、Vue3 Hello World 四步流程
从一个最简示例入手,理解 Vue3 应用从零到渲染的完整过程。以下代码可以在浏览器中直接打开运行。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue3 Hello World</title>
</head>
<body>
<!-- 第1步:引入 Vue3 源代码(CDN 方式) -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<!-- 第2步:准备 HTML 挂载容器 -->
<div id="app">
<!-- 第4步:使用插值语法渲染数据 -->
<h1>{{ msg }}</h1>
<p>我是 {{ obj.name }},今年 {{ obj.age }} 岁</p>
<p>{{ obj.age >= 18 ? '成年了' : '未成年' }}</p>
<p>来年就 {{ obj.age + 1 }} 岁了</p>
<p>fn 的返回值 = {{ fn() }}</p>
<p>{{ msg }} 由 {{ msg.split(' ').length }} 个单词组成</p>
</div>
<script>
// 第3步:创建 Vue 应用实例并定义业务数据
Vue.createApp({
setup() {
const msg = 'hello world'
const obj = {
name: '张三',
age: 9
}
const fn = () => {
return 666
}
// setup 中声明的数据必须 return 才能被模板使用
return { msg, obj, fn }
}
}).mount('#app') // 把内容挂载到 id="app" 的容器里
</script>
</body>
</html>
四步流程拆解:
| 步骤 | 操作 | 关键代码 | 说明 |
|---|---|---|---|
| 1. 引入 Vue | 加载 Vue3 核心库 | <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> |
本地文件或 CDN 均可,引入后全局可用 Vue 对象 |
| 2. 准备容器 | 定义挂载点 | <div id="app">...</div> |
Vue 只控制该容器内的 DOM,容器外不受影响 |
| 3. 创建实例 | 调用 createApp 并配置 setup |
Vue.createApp({ setup() { ... } }) |
setup 是组合式 API 的入口,数据和方法都在这里定义 |
| 4. 渲染数据 | 使用插值语法 {``{ }} |
{``{ msg }} |
将 JS 表达式的结果渲染到 HTML 标签内部 |
思考题 :如果页面上有两个
<div>,一个id="app",一个id="root",只有#app内的{``{ msg }}会被解析------因为mount('#app')指定了挂载目标,Vue 只管理它挂载的那个容器。
三、setup 入口函数
setup 是 Vue3 专属 的组合式 API 入口函数,所有组合式 API 的代码都在 setup 函数内部编写,可以把它理解为整个组件的"逻辑中枢"。
核心规则:
| 规则 | 说明 |
|---|---|
| 变量/方法必须声明在 setup 内 | 模板中用到的数据、函数,都必须在 setup 中定义 |
| 必须 return 暴露出去 | 只有 return 出去的数据,模板才能访问到 |
setup 内的 this 不是 Vue 实例 |
与 Vue2 的 Options API 完全不同,setup 中不使用 this 关键字 |
典型结构:
javascript
Vue.createApp({
setup() {
// 1. 在这里定义数据
const message = 'Hello'
// 2. 在这里定义方法
const sayHi = () => {
console.log('Hi')
}
// 3. 必须 return 出去,模板才能用
return {
message,
sayHi
}
}
}).mount('#app')
插值语法(声明式渲染 / 胡子语法)
语法 :{``{ JS表达式 }}
作用:将表达式的运算结果渲染到 HTML 标签的内部文本中。
合法的表达式类型:
| 类型 | 示例 | 渲染结果 |
|---|---|---|
| 变量 | {``{ msg }} |
hello world |
| 字面常量 | {``{ 100 }} |
100 |
| 对象属性取值 | {``{ obj.name }} |
张三 |
| 三元运算 | {``{ obj.age >= 18 ? '成年' : '未成年' }} |
未成年 |
| 算术运算 | {``{ obj.age + 1 }} |
10 |
| 函数/方法调用 | {``{ fn() }} |
666 |
| 组合表达式 | {``{ msg.split(' ').length }} |
2 |
⚠️ 注意 :
{``{ }}中只能写单一 JS 表达式 ,不能写if/for/while等语句块。表达式必须有返回值,if语句没有返回值,所以不能在插值中使用。
四、响应式核心概念
在 Vue 的响应式体系中,有三个关键角色:
┌──────────┐ 修改数据 ┌──────────────┐ 自动更新 ┌──────────┐
│ 数据 │ ──────────────────▶ │ 响应式系统 │ ──────────────────▶ │ 视图 │
│ (state) │ │ (Reactivity) │ │ (View) │
└──────────┘ └──────────────┘ └──────────┘
▲ │
└──────────────────── 用户交互(事件)────────────────────────────────┘
| 角色 | 定义 | 在代码中的体现 |
|---|---|---|
| 数据 | setup 函数中 return 返回的变量和方法 |
const msg = ref('hello') |
| 视图 | HTML 页面的 DOM 标签 | <div id="app">...</div> 内的内容 |
| 数据响应式 | 数据变化后,页面视图自动同步更新 | 修改 msg.value,页面无需手动刷新 |
Vue 的核心思想:数据驱动视图 ------ 开发者只需关注数据的变化,Vue 自动完成 DOM 更新,彻底告别手动
document.querySelector+innerHTML的时代。
对比传统做法 vs Vue 做法:
javascript
// ❌ 传统做法:手动操作 DOM(不仅繁琐,而且容易出错)
document.querySelector('#name').innerHTML = '新名字'
// ✅ Vue 做法:只修改数据,视图自动更新
obj.name = '新名字' // 页面自动变化,无需操作 DOM
⚠️ 关键陷阱:普通变量不具备响应式
这是 Vue3 新手最容易踩的坑。在 setup 中直接声明的普通变量,修改后页面不会更新:
javascript
setup() {
// ❌ 这样声明的变量没有响应式 ------ 改了数据,页面纹丝不动!
let count = 0
const add = () => {
count++ // 数据确实变了,但页面不会更新
}
return { count, add }
}
原因 :Vue 的响应式依赖 JavaScript 的
Proxy或Object.defineProperty来拦截数据的读取和修改,普通变量不具备这种拦截能力。必须使用reactive或ref这两个 API 来包裹数据,才能让 Vue "感知"到数据的变化。
五、reactive 响应式 API
reactive 用于将对象类型 的数据转换为响应式代理对象,底层基于 ES6 的 Proxy 实现。
| 特性 | 说明 |
|---|---|
| 作用 | 让对象/数组类型数据具备响应式 |
| 参数限制 | 只能传入对象或数组,不能传字符串、数字等基础类型 |
| 返回值 | 返回一个 Proxy 代理对象,用法和原对象完全一致 |
传基本数据类型:
完整代码示例
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue3 reactive 示例</title>
</head>
<body>
<div id="app">
<p>我叫 {{ obj.name }},今年 {{ obj.age }} 岁</p>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
// 第1步:从 Vue 中解构导入 reactive
const { createApp, reactive } = Vue
createApp({
setup() {
// 第2步:传入对象,生成响应式代理对象
// ✅ reactive 的参数必须是对象/数组
const obj = reactive({
name: '张三',
age: 18
})
// ❌ 基础类型会报警告,且不具备响应式
// const num = reactive(100) // 不要这样做!
// 第3步:return 暴露出去
return { obj }
}
}).mount('#app')
</script>
</body>
</html>
代码思路解析:
- 从 Vue 中解构导入 :
const { createApp, reactive } = Vue,这是 ES6 的解构赋值写法。 - 包裹对象 :
reactive({ name: '张三', age: 18 })返回一个响应式代理对象obj。 - 验证响应效果 :在浏览器控制台中,直接输入
obj.name = '李四',你会发现页面上的名字立刻自动更新------这就是响应式的威力。
reactive 的限制:
javascript
// ❌ 不能用于基础类型 ------ 这是 reactive 最大的限制
const count = reactive(0) // 报错或无效
const name = reactive('hello') // 报错或无效
const flag = reactive(true) // 报错或无效
// ✅ 只能用于对象/数组
const user = reactive({ name: 'Tom', age: 20 }) // 正确
const list = reactive([1, 2, 3]) // 正确
底层原理补充 :
reactive底层使用Proxy代理整个对象。基础类型(数字、字符串等)在 JS 中是按值传递的,Proxy无法代理一个"值",只能代理一个"对象引用"。所以reactive天然无法处理基础类型------这也是为什么需要ref的原因。
六、ref 响应式 API
ref 是比 reactive 适用范围更广的响应式 API,既能处理基础类型,也能处理引用类型。
| 特性 | 说明 |
|---|---|
| 作用 | 生成响应式数据,全类型兼容 |
| 基础类型支持 | ✅ 数字、字符串、布尔值 |
| 引用类型支持 | ✅ 对象、数组(内部自动调用 reactive) |
| 访问方式 | 模板中直接使用,JS 中需 .value |
完整代码示例
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue3 ref 示例</title>
</head>
<body>
<div id="app">
<!-- 模板中使用:直接写变量名,无需 .value -->
<p>{{ mes }}</p>
<p>我叫 {{ obj.name }},今年 {{ obj.age }} 岁了</p>
<button @click="onClick">修改 ref 数据</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
// 第1步:从 Vue 中解构导入 ref
const { createApp, ref } = Vue
createApp({
setup() {
// 第2步:传入任意类型数据,生成响应式包装对象
// ref 定义基础类型 ------ 字符串
const mes = ref('hello world')
// ref 定义引用类型 ------ 对象
const obj = ref({
name: '小vue',
age: 9
})
// 定义点击事件,修改 ref 数据
const onClick = () => {
// 第3步:JS 中修改/取值必须通过 .value
mes.value = '你好世界'
// 修改 ref 包裹的对象属性
obj.value.name = 'Vue3'
obj.value.age = 10
}
return { mes, obj, onClick }
}
}).mount('#app')
</script>
</body>
</html>
.value 规则详解(面试高频考点):
| 场景 | 写法 | 说明 |
|---|---|---|
| HTML 模板中取值 | {``{ mes }} |
模板自动解包,不需要 .value |
| HTML 模板中取对象属性 | {``{ obj.name }} |
对象也会自动解包,不需要 .value |
| JS 代码中取值 | console.log(mes.value) |
必须 用 .value 访问 |
| JS 代码中修改 | mes.value = '新值' |
必须 用 .value 赋值 |
| JS 中修改对象属性 | obj.value.name = '新名字' |
先 .value 拿到对象,再 .name 修改属性 |
代码思路解析:
javascript
// ref('hello world') 的内部简化结构
const mes = ref('hello world')
// mes 的本质 ≈ { value: 'hello world' }
// └── Vue 对这个对象做了响应式代理
// └── 模板中 Vue 会自动"扒开"这层壳,所以不需要 .value
// └── JS 中你必须手动"扒开",用 .value 操作
// ref 接收对象时,内部其实是转交给了 reactive
const obj = ref({ name: '小vue', age: 9 })
// 内部等价于:
// ref({ name, age }) → { value: reactive({ name, age }) }
记忆口诀 :模板自动解包不用
.value,JS 手动操作必须.value。
七、reactive vs ref 选型方案
两者对比:
| 对比维度 | reactive | ref |
|---|---|---|
| 支持基础类型 | ❌ 不支持 | ✅ 支持 |
| 支持对象/数组 | ✅ 支持 | ✅ 支持(内部转调 reactive) |
| JS 中访问方式 | 直接使用,无 .value |
必须 .value |
| 模板中访问方式 | 直接使用 | 自动解包,直接使用 |
| 底层实现 | Proxy 代理 |
对象包装 + Proxy(对象类型时) |
| 解构是否保持响应式 | ❌ 解构会丢失响应式 | ❌ 解构也会丢失(需用 toRefs) |
核心原则 :能用
reactive就用reactive,用不了的就用ref。
✅ 优先用 reactive 的场景 ------ 表单固定字段对象
表单的字段结构通常是预先定义好的,不会有动态新增/删除属性的需求,这是 reactive 的最佳使用场景:
javascript
const loginForm = reactive({
username: '',
password: '',
rememberMe: false
})
// 直接修改属性即可触发响应式更新
loginForm.username = 'admin'
loginForm.password = '123456'
为什么用 reactive? 表单对象的字段是固定的,不需要 .value 访问,代码更简洁直观。
✅ 使用 ref 的场景:
场景一:对象属性不确定(后端异步赋值)
javascript
// 初值不确定,后端返回数据后再整体替换
const user = ref({})
// ... 异步请求后 ...
user.value = { name: '张三', age: 30, role: 'admin' } // 整体替换
如果用
reactive,整体替换对象会丢失响应式引用(因为换了一个新对象);但ref通过.value赋值,Vue 能正确追踪到变化。
场景二:响应式数组
javascript
const list = ref([])
// 后端返回列表数据后直接赋值
list.value = [1, 2, 3, 4, 5]
场景三:基础类型(数字、字符串、布尔)
javascript
// 这没得选 ------ reactive 不支持基础类型
const count = ref(0)
const message = ref('hello')
const isLoading = ref(false)
const isVisible = ref(true)
选型决策速查:
要定义的数据是什么类型?
│
├── 基础类型(string / number / boolean)
│ └── ✅ 只能用 ref
│
├── 数组
│ └── ✅ 推荐 ref :方便整体替换(如后端返回新数组)
│
└── 对象
├── 字段固定、结构明确(如表单)
│ └── ✅ 推荐 reactive :代码简洁,无需 .value
│
└── 字段不确定、需整体替换(如后端异步数据)
└── ✅ 推荐 ref :支持整体赋值
全文总结
本文从 Vue3 的最基础概念出发,按照概念 → 原理 → 实战 → 选型的递进逻辑,系统梳理了以下内容:
- Vue 整体定位:渐进式 JavaScript 框架,核心是"构建用户界面",可轻量嵌入也可整站开发。
- Hello World 四步流程:引入源码 → 准备容器 → 创建实例 → 插值渲染,这是每个 Vue3 应用的骨架。
- setup 入口函数:组合式 API 的起点,变量和方法都在此定义并 return 暴露。
- 响应式核心概念:数据驱动视图,数据变则视图自动变,Vue 的核心价值所在。
- reactive:面向对象/数组的响应式 API,基于 Proxy 实现,不能处理基础类型。
- ref :全类型兼容的响应式 API,基础类型直接包装,对象类型内部转调 reactive,JS 中访问需
.value。 - 选型方案 :表单固定对象用
reactive,其余场景(基础类型、数组、需整体替换的对象)用ref。
核心知识点复盘
| 序号 | 知识点 | 一句话总结 |
|---|---|---|
| 1 | Vue3 应用创建 | createApp({ setup() {...} }).mount('#app') |
| 2 | setup 函数 | 组合式 API 入口,数据必须 return 才能被模板使用 |
| 3 | 插值语法 | {``{ JS表达式 }},支持变量、三元、算术、函数调用等 |
| 4 | 响应式定义 | 数据变化 → 视图自动更新,Vue 的核心思想 |
| 5 | reactive | 仅支持对象/数组,直接访问属性,无需 .value |
| 6 | ref | 全类型支持,JS 中必须 .value,模板中自动解包 |
| 7 | ref 本质 | ref(x) ≈ 把 x 包进 { value: x },并对其做响应式代理 |
| 8 | 解构陷阱 | reactive 和 ref 解构后都会丢失响应式(需 toRefs) |
常见问题 / 避坑指南
❓ 1. 为什么 setup 里的普通变量改了页面不更新?
javascript
// ❌ 错误写法
let count = 0
count++ // 数据变了,页面没反应
原因 :普通变量不具备响应式,Vue 无法追踪其变化。必须使用 ref 或 reactive 包裹。
javascript
// ✅ 正确写法
const count = ref(0)
count.value++ // 页面自动更新
❓ 2. reactive 能不能用来包裹基础类型?
不能。 reactive 的参数必须是对象或数组。传入基础类型会失效或报错。如果你需要给一个数字做响应式,直接用 ref。
javascript
// ❌ 不可行
const num = reactive(100) // 无效,num 不具备响应式
// ✅ 正确做法
const num = ref(100)
❓ 3. 忘记写 .value 怎么办?
javascript
const msg = ref('hello')
msg = 'world' // ❌ 这样相当于把 ref 对象整个替换掉了,响应式丢失!
// ✅ 正确:
msg.value = 'world'
记忆技巧 :把
ref想象成一个盒子,数据装在盒子里。在 JS 中操作时,必须打开盒子(.value);在模板中,Vue 帮你自动打开。
❓ 4. 模板里 {``{ obj.name }} 为什么不写成 {``{ obj.value.name }}?
Vue 的模板编译器会自动"解包"顶层的 ref 对象------这个特性叫做 ref 的自动解包(unwrap) 。所以模板中不需要 .value。
html
<!-- 模板中自动解包 -->
<p>{{ obj.name }}</p> <!-- ✅ 正确 -->
<!-- 不需要这样写 -->
<p>{{ obj.value.name }}</p> <!-- ❌ 错误 -->
❓ 5. reactive 对象解构后还保持响应式吗?
不会! 这是 reactive 的一个常见坑。
javascript
const state = reactive({ count: 0, name: 'Vue' })
// ❌ 解构会丢失响应式!
const { count, name } = state
count++ // 页面不会更新
// ✅ 需要搭配 toRefs 保留响应式
const { count, name } = toRefs(state)
count.value++ // 页面正常更新
❓ 6. 如何决定用 CDN 引入还是工程化搭建?
| 场景 | 推荐方式 |
|---|---|
| 学习阶段、写 Demo、简单小工具 | CDN 引入(本文所有示例均适用) |
| 公司项目、中大型应用、多人协作 | 工程化搭建(Vite + Vue3 + Pinia) |
📝 写在最后 :Vue3 的响应式系统是其灵魂所在。理解了
ref和reactive的本质区别和适用场景,就掌握了 Vue3 组合式 API 最核心的一块拼图。建议读者将本文中的代码示例亲手敲一遍,并在浏览器控制台中修改数据,亲眼观察页面的变化------这是理解响应式最快的方式。
