本章围绕三个核心问题展开:Vue 为什么值得学、Vue 如何把数据渲染成界面、数据变化后界面为什么会自动变化。
学完这一章,应该能够不依赖工程化脚手架,在一个普通 HTML 文件中完成 Vue 3 的基本运行;能够说清楚
createApp()、mount()、data()、methods、插值表达式、v-for、key、事件处理以及响应式数据之间的关系;同时能够理解传统 CDN 开发方式与 Vite 工程化开发方式各自适合什么场景。
友情提示,这个还是需要大家在看过了前面的前端基础课之后进行哦~~
一、为什么现在仍然要学 Vue 3
前端页面从来不只是"把几个标签写到浏览器里"。真正的业务页面通常会不断发生变化:用户登录后要显示昵称,购物车数量会变化,表格数据来自服务器,搜索关键字变化后列表要更新,点击按钮后状态要切换,后端接口返回新数据以后界面还要重新展示。
如果只使用原生 JavaScript,这些事情当然都能完成。问题在于:随着页面状态越来越多,开发者会不断重复"找到 DOM → 修改 DOM → 再找到另一个 DOM → 再修改 DOM"的工作。代码数量会增长,界面状态与 JavaScript 数据之间也更容易出现不同步。
Vue 解决的核心问题之一,就是把开发关注点从"我下一步应该操作哪个 DOM"转向"现在的数据是什么,界面应该长什么样"。
这也是理解 Vue 的第一把钥匙:
Vue 并不是让浏览器拥有新的能力,而是用一套更清晰、更可维护的规则,帮助我们组织数据、界面以及两者之间的更新关系。
1. Vue 对实际开发最直接的价值
Vue 对项目开发的价值,可以概括成几个非常具体的方面。
第一,减少重复 DOM 操作。很多原生 JavaScript 中必须手工完成的 DOM 更新,可以交给 Vue 根据数据状态自动完成。
第二,提高界面代码的可读性。模板中直接描述"数据满足什么条件时显示什么""数组中有多少项就渲染多少项",代码更接近业务表达。
第三,提高大型项目的可维护性。后续进入组件化以后,一个复杂页面可以拆成多个职责明确的组件;路由、状态管理、UI 组件库等生态工具可以继续向上叠加。
第四,适合和各种后端技术配合。Spring Boot、Node.js、Python、Go、PHP,甚至 C++ 提供的 Web 接口,本质上都可以向浏览器提供 HTTP API。Vue 负责前端交互和界面展示,后端负责业务、数据库、权限等,两者并不冲突。
因此,"学 Vue"并不是只为了学一种语法,而是在掌握一种更高效地组织现代 Web 用户界面的方式。
二、先看一个最典型的问题:根据数组生成 <li>
假设页面中有一个数组:
js
const names = ['Java', 'Vue', 'Spring Boot']
希望最终页面得到:
html
<ul>
<li>Java</li>
<li>Vue</li>
<li>Spring Boot</li>
</ul>
这个需求非常简单,但它特别适合对比原生 JavaScript 与 Vue 的思想差异。
1. 原生 JavaScript:告诉浏览器每一步怎么做
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>原生 JS 列表渲染</title>
</head>
<body>
<!-- JavaScript 稍后会找到这个 ul,并向里面添加 li -->
<ul id="list"></ul>
<script>
// 1. 这份数组是当前真正的业务数据。
// 页面应该根据这里有多少项,最终生成多少个 li。
const names = ['Java', 'Vue', 'Spring Boot']
// 2. 通过 document.querySelector 主动查找页面中的 ul。
// 参数 '#list' 是 CSS 选择器字符串。
// 返回值是匹配到的第一个 Element;如果没有匹配到,则返回 null。
const list = document.querySelector('#list')
// 3. 遍历数组。
// forEach 会依次调用回调函数,每次把当前数组元素交给 name 参数。
// forEach 本身主要用于执行遍历动作,它的返回值是 undefined。
names.forEach((name) => {
// 4. 主动创建一个新的 li DOM 元素。
// createElement('li') 的参数是标签名,返回一个新创建的 HTMLElement。
const li = document.createElement('li')
// 5. 把当前数组元素作为 li 的文本内容。
// 使用 textContent 会把字符串当普通文本处理,而不是当 HTML 解析。
li.textContent = name
// 6. 再主动把 li 插入 ul。
// appendChild 接收一个 Node,并把它添加为当前节点的最后一个子节点。
// 返回值就是刚刚被插入的那个节点。
list.appendChild(li)
})
</script>
</body>
</html>
这段代码没有错误,而且在小页面中完全可以工作。但注意它的思维过程:
- 找到容器;
- 遍历数组;
- 创建元素;
- 设置文本;
- 插入元素。
这就是典型的命令式编程:开发者不仅要说"结果是什么",还要亲自告诉程序"为了得到这个结果,每一步应该怎么操作"。
2. 原生写法真正麻烦的地方在"后续变化"
如果只是第一次生成列表,代码并不算长。问题出现在数据变化以后。
例如:
js
names.push('MySQL')
数组里确实已经增加了 'MySQL',但页面不会凭空多出一个 <li>MySQL</li>。
原因非常重要:
普通 JavaScript 数组与 DOM 之间没有自动绑定关系。
names 是 JavaScript 数据,<li> 是页面 DOM。你修改数组,浏览器并不知道你希望同步修改哪个 DOM。
因此,开发者还要继续写 DOM 更新代码。业务越来越复杂以后,很容易出现:数据是新值,但页面还是旧值;或者页面改了,数据又没同步回去。
三、Vue 的做法:描述"数据应该如何显示"
同样的列表,如果使用 Vue,可以写成:
html
<div id="app">
<ul>
<!--
v-for 用来根据数组重复渲染当前 li。
name 是每次循环时的当前元素。
names 是 data() 中声明的响应式数组。
-->
<li v-for="name in names" :key="name">
{{ name }}
</li>
</ul>
</div>
这里最关键的一句话是:
html
<li v-for="name in names">{{ name }}</li>
它表达的不是"先创建 li,再设置文本,再插入 DOM",而是:
对于
names中的每一项,都渲染一个<li>,并把当前项的值显示出来。
开发者描述的是界面与数据之间的关系。
这就是 Vue 中"声明式渲染"的核心感觉。
第一部分:Vue 的背景与定位
四、Vue 到底是什么
Vue 官方将其定位为一个用于构建 Web 用户界面的渐进式 JavaScript 框架。
把这句话拆开以后,至少有四个关键词:
- JavaScript
- 用户界面
- 渐进式
- 框架
如果这四个词没有理解清楚,后面的语法很容易变成死记硬背。
五、什么叫"构建用户界面"
"用户界面"就是用户在浏览器中真正看到、点击、输入和操作的部分,例如:
- 标题;
- 导航栏;
- 商品卡片;
- 表格;
- 输入框;
- 按钮;
- 对话框;
- 分页器;
- 登录状态;
- 购物车数量。
所谓"构建用户界面",可以先用一句非常朴素的话理解:
把程序中的数据,按照一定规则渲染成用户能够看到和操作的页面。
例如程序里有:
js
const user = {
name: '张三',
level: 8
}
最终页面可能显示:
text
用户名:张三
等级:8
真正驱动页面内容的是数据。Vue 要解决的关键问题,就是让"数据 → 页面"的过程更清晰,并且在数据变化后自动更新相关页面。
六、什么叫"渐进式"
"渐进式"最容易被误解成"Vue 比较简单"。它真正强调的是:Vue 可以按需求逐步采用。
一个已有的传统网站,不一定要推倒重做。你可以只在某个局部模块中引入 Vue,让 Vue 管理一个商品筛选区、一个表格或者一个交互面板。
如果项目规模继续扩大,还可以进一步使用:
- 单文件组件;
- Vite;
- Vue Router;
- Pinia;
- Element Plus 等 UI 组件库。
因此,Vue 的使用方式可以从"页面里的一个小模块"逐步扩展到"完整的前端应用"。
两种典型开发形态
形态一:只引入 Vue 核心包
典型场景:
- 给已有 HTML 页面增加交互;
- 后端模板页面做局部增强;
- 教学演示;
- 很小的工具页面;
- 不需要构建工具的局部模块。
最简单的方式就是通过 <script> 引入 Vue 的浏览器构建版本。
形态二:Vue + Vite + 生态工具
典型场景:
- 完整后台管理系统;
- 单页应用;
- 中大型前端项目;
- 需要组件化、路由、状态管理、构建优化的项目。
当前 Vue 官方对新项目推荐的是 create-vue 创建的 Vite 项目,而不是旧的 Vue CLI。
七、什么叫"框架"
可以把"框架"理解成一套已经设计好的开发规则、组织方式和运行机制。我们之前学习过的langchain就是一种框架。
使用原生 JavaScript 时,很多事情由开发者自己决定:数据放哪、DOM 怎么找、更新顺序如何组织、页面如何拆分。
进入 Vue 以后,你会逐步学习一套固定规则,例如:
- 数据可以怎样声明;
- 模板怎样读取数据;
- 指令怎样写;
- 事件怎样绑定;
- 组件怎样组织;
- 组件之间怎样通信;
- 路由怎样管理页面;
- 状态怎样集中管理。
学习 Vue,本质上就是:理解这些规则为什么存在,然后熟练使用这些规则解决界面开发问题。
八、Vue 3 与 Vue 2 应该怎样看待
现在学习 Vue,应把重点放在 Vue 3。
Vue 官方 FAQ 明确说明:Vue 3 是当前最新主版本;Vue 2 已在 2023 年 12 月 31 日结束官方支持。
因此,在新项目和新的学习路线中,不应该再把 Vue 2 的写法当作默认标准。
这意味着看到以下旧写法时,要知道它属于 Vue 2 时代:
js
new Vue({
el: '#app'
})
Vue 3 的应用入口改为 createApp():
js
Vue.createApp({
// 根组件配置
}).mount('#app')
这不是简单的"换了一个名字",而是 Vue 3 对应用实例作用域进行重新设计后的 API 形式。
第二部分:Vue 应该学习哪些内容,以及怎样学习
九、Vue 的学习内容应该怎样分层
Vue 的完整生态很大,但学习不应该一口气把所有 API 都塞进同一阶段。
可以按下面的逻辑逐层推进:
第一层:Vue 基础
重点是:
- Vue 是什么;
- 如何创建应用;
- 模板与数据;
- 插值表达式;
- 常用指令;
- 事件;
- 表单;
- 计算属性;
- 侦听器;
- 响应式基础。
第二层:组件化
重点是:
- 组件;
- 生命周期;
- props;
- emit;
- 组件通信;
- 插槽;
- 组件拆分思想。
第三层:完整应用能力
重点是:
- Vue Router;
- Pinia;
- 接口请求;
- 权限与页面组织;
- Element Plus 等组件库;
- Vite 工程化;
- 打包与部署。
本章只把第一层最前面的内容讲透:背景、环境、声明式渲染和响应式。
十、学习 Vue 最有效的方式
Vue 语法看起来通常不难,真正的难点是:看懂不等于会写,会写不等于能独立解决问题。
更有效的方法是:
- 每个语法先理解"它解决什么问题";
- 自己从空文件写一遍,不只复制;
- 主动改变数据观察结果;
- 主动写错,再看控制台错误;
- 用一个小案例同时组合两三个知识点;
- 遇到不确定的 API 优先查官方文档;
- 把容易混淆的点自己总结出来。
例如学完 v-for,不要只满足于渲染三条字符串。可以继续自己修改:
- 数组改成对象数组;
- 显示索引;
- 增加
key; - 点击按钮
push一条数据; - 点击另一按钮删除一条数据。
这样才能真正看到"数据变化 → Vue 自动更新页面"的效果。
第三部分:准备 Vue 代码环境
十一、编辑器:VS Code
Vue 可以使用多种编辑器开发,但官方工具链文档仍推荐 VS Code 配合 Vue - Official 扩展。
VS Code 本身负责代码编辑、文件管理、终端、调试等;Vue - Official 为 .vue 单文件组件提供 Vue 相关的语法理解和智能提示。
1. 推荐安装:Vue - Official
该扩展以前常被称为 Volar,现在在 VS Code 扩展市场中的名称是 Vue - Official。
它主要提供:
- Vue 单文件组件语法高亮;
- 模板表达式智能提示;
- 组件、props、事件、插槽等补全;
- TypeScript 类型检查;
- 错误诊断;
- 跳转定义;
- 重构能力;
- Vue 文件格式化支持。
需要特别注意:Vue 官方已经明确说明,Vue - Official 取代了过去面向 Vue 2 的 Vetur。Vue 3 项目中如果同时启用旧 Vetur,可能产生冲突,因此不应把 Vetur 当作当前 Vue 3 的默认插件。
2. Vue 3 Snippets:可选
代码片段插件的价值是快速生成常见模板,例如输入缩写后展开一段 Vue 结构。
它属于"提高输入效率"的工具,不是 Vue 能否运行的前提,也不是 Vue 语法本身。
因此可以安装,但不要形成"离开 snippets 就不会写代码"的依赖。真正需要记住的是 Vue 文件结构与语法含义。
3. Path Intellisense:可选
它主要用于路径补全。当项目中文件层级多、导入语句多时,路径提示会比较方便。
不过现代 VS Code 自身也已经具有相当多的路径与模块提示能力,因此它属于锦上添花,而不是必装条件。
4. Auto Import:谨慎按需安装
"自动导入"类扩展可以在输入某个变量或函数时自动生成 import。
它能节省时间,但也要注意:现代编辑器、TypeScript 语言服务以及 Vue 工具本身已经能够提供不少自动导入功能。安装第三方 Auto Import 前最好确认它确实解决了当前项目的问题,避免多个插件重复工作。
5. Auto Close Tag、Auto Rename Tag:很多场景下已经不是必需
VS Code 现在对 HTML 已经内置自动闭合标签能力,也支持 linked editing,在修改开始标签时同步更新匹配的结束标签。
所以过去常见的 Auto Close Tag、Auto Rename Tag 扩展,现在不一定必须安装。
这体现了一个很重要的工具使用原则:
插件不是越多越好。优先使用编辑器和 Vue 官方插件已经提供的能力。
第四部分:Vue 代码到底写在哪里
十二、传统开发方式:直接在 HTML 中使用 Vue
传统方式不需要 Vite,不需要 npm 项目,也不需要 .vue 文件。
一个 HTML 文件就可以运行:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue 3 CDN Demo</title>
</head>
<body>
<div id="app">
{{ message }}
</div>
<!--
通过 CDN 加载 Vue 3 的全局构建版本。
加载完成后,页面会得到一个全局变量 Vue。
createApp、ref、reactive 等顶层 API 都可以从 Vue 上取得。
-->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
// 从全局 Vue 对象中解构出 createApp 函数。
const { createApp } = Vue
// createApp 创建 Vue 应用实例。
// 这里传入的对象就是这个小应用的根组件配置对象。
const app = createApp({
data() {
// data() 必须返回一个对象。
// 返回对象中的属性会成为当前组件的响应式状态。
return {
message: 'Hello Vue 3!'
}
}
})
// mount('#app') 把应用挂载到 id="app" 的 DOM 容器中。
// 挂载完成后,容器中的模板会由 Vue 接管和渲染。
app.mount('#app')
</script>
</body>
</html>
传统方式的优点
- 文件少;
- 几乎零配置;
- 方便理解 Vue 最基础的运行过程;
- 适合给已有页面做局部增强;
- 适合很小的页面。
传统方式的限制
通过 CDN 直接使用 Vue 时,没有常规工程化构建步骤,因此无法直接享受完整的 .vue 单文件组件开发体验。
项目一旦变大,模块拆分、依赖管理、开发服务器、构建优化等需求都会出现,这时就应该进入 Vite 工程化模式。
十三、工程化开发:当前新项目优先使用 Vite
当前 Vue 官方推荐通过 create-vue 创建基于 Vite 的 Vue 项目。
官方当前快速上手文档要求 Node.js 版本满足:
text
^20.19.0 || >=22.12.0
创建命令:
bash
npm create vue@latest
这条命令会执行 Vue 官方的项目脚手架 create-vue。
创建完成后通常继续:
bash
cd 项目目录
npm install
npm run dev
三个命令分别表示:
cd 项目目录:进入新创建的项目文件夹;npm install:根据package.json安装项目依赖;npm run dev:执行开发脚本,启动 Vite 开发服务器。
为什么不再把 Vue CLI 当作新项目默认方案
Vue CLI 是过去非常重要的 Vue 工具链,它主要基于 webpack。
但 Vue 官方目前已经把 Vue CLI 标记为维护模式,并明确建议新项目使用 create-vue 创建 Vite 项目。
因此现在讲"工程化 Vue 3",默认应该想到:
text
Vue 3 + create-vue + Vite
而不是:
text
Vue 3 + Vue CLI
Vue CLI 并不是完全不能运行,而是不再是新项目首选。
第五部分:Vue 3 Hello World 的完整运行过程
十四、第一步:准备挂载容器
html
<div id="app"></div>
这个 <div> 是 Vue 应用将要接管的容器。
id="app" 本身不是 Vue 的强制规定。你也可以写:
html
<div id="root"></div>
然后:
js
app.mount('#root')
两边选择器一致即可。
十五、第二步:引入 Vue
全局 CDN 方式:
html
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
这个脚本加载成功以后,会在浏览器全局环境中提供 Vue 对象。
因此可以写:
js
const { createApp } = Vue
这句是 JavaScript 的对象解构语法,等价理解为:
js
const createApp = Vue.createApp
十六、第三步:调用 createApp() 创建应用
js
const app = createApp({
data() {
return {
message: 'Hello Vue!'
}
}
})
createApp() 的作用
createApp() 用于创建一个 Vue 应用实例。
官方类型可以概括为:
ts
function createApp(rootComponent: Component, rootProps?: object): App
参数一:rootComponent
必传。
它表示根组件。
在 CDN 的简单例子里,经常直接传一个组件选项对象:
js
createApp({
data() {
return {
message: 'Hello'
}
}
})
在 Vite 单文件组件项目里,通常传入导入的 App:
js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App)
参数二:rootProps
可选。
它用于向根组件传入 props。
在最基础的学习和普通应用入口中通常不需要写,因此看到:
js
createApp(App)
完全正常。
返回值
返回一个 Vue 应用实例 App。
这个应用实例后续可以调用:
mount();use();component();directive();provide()等。
当前阶段最重要的是 mount()。
十七、第四步:调用 app.mount() 挂载
js
app.mount('#app')
mount() 的作用
将刚刚创建的 Vue 应用挂载到一个实际 DOM 容器中。
如果没有 mount(),即使已经执行了 createApp(),页面也不会真正渲染这个应用。
可以把它理解成:
text
createApp():把 Vue 应用"创建出来"
mount():告诉 Vue"把这个应用运行到页面的这个位置"
mount() 的参数
可以传:
- CSS 选择器字符串;
- 实际 DOM 元素。
最常见:
js
app.mount('#app')
也可以:
js
const container = document.querySelector('#app')
app.mount(container)
mount() 的返回值
mount() 返回根组件的公开实例,而不是应用实例本身。
因此:
js
const vm = app.mount('#app')
这里的 vm 是根组件公开实例。
在基础阶段通常不需要专门保存这个返回值,所以最常见仍然是:
js
app.mount('#app')
一个非常容易写错的点
应用级配置应该在 mount() 之前完成。
正确思路:
js
const app = createApp(App)
// 这里做 app.use()、app.component() 等配置
app.mount('#app')
挂载可以理解成应用正式进入页面运行阶段。
第六部分:data()------选项式 API 中最核心的响应式数据入口
十八、data() 是什么
在选项式 API 中,data 是一个函数,用于声明组件初始响应式状态。
最典型写法:
js
createApp({
data() {
return {
message: 'Hello Vue',
count: 0,
names: ['Java', 'Vue', 'Spring Boot']
}
}
}).mount('#app')
data() 的返回值
data() 应返回一个普通 JavaScript 对象。
例如:
js
return {
count: 0
}
Vue 会把这个返回对象转换成响应式状态。
因此模板可以直接读取:
html
<p>{{ count }}</p>
组件方法中可以通过:
js
this.count
访问它。
data() 为什么是函数而不是直接对象
组件设计中,每个组件实例都应该拥有自己的状态。使用函数返回对象,可以让不同实例获得各自的数据对象。
在本章只有一个根组件时,这个优势还不明显。进入组件化以后就会非常重要。
data() 中应提前声明将使用的顶层状态
例如页面稍后要使用 username,但开始时还没有值,可以先写:
js
data() {
return {
username: null
}
}
而不是等组件运行很久以后才突然添加这个顶层属性。
这样代码结构更清晰,状态也更容易理解。
data() 不推荐写成箭头函数
不要写:
js
data: () => {
return {
count: 0
}
}
特别是在需要通过 this 访问组件实例的场景中,箭头函数不会建立 Vue 期望的组件 this 绑定。
基础学习阶段统一写成:
js
data() {
return {
count: 0
}
}
最稳妥。
第七部分:声明式渲染到底是什么
十九、从"操作 DOM"转成"描述结果"
原生命令式写法关注:
text
第一步做什么
第二步做什么
第三步做什么
Vue 声明式写法关注:
text
当前数据是这样时,界面应该长这样
例如:
html
<h2>{{ title }}</h2>
这句话表达的是:
这个
<h2>的文本内容来自响应式数据title。
当 title 更新时,不需要重新写:
js
document.querySelector('h2').textContent = 新值
Vue 会根据数据依赖关系更新对应界面。
这就是声明式渲染最重要的开发体验。
二十、插值表达式 {``{ }}
最基础的数据绑定形式是文本插值,也常叫 Mustache 语法:
html
<p>{{ message }}</p>
如果:
js
data() {
return {
message: '你好,Vue 3'
}
}
最终页面显示:
text
你好,Vue 3
插值表达式内部可以写什么
插值中可以使用能够求值的 JavaScript 表达式,例如:
html
<p>{{ count + 1 }}</p>
<p>{{ message.toUpperCase() }}</p>
<p>{{ ok ? '开启' : '关闭' }}</p>
这些都是"表达式",能够计算出一个值。
插值表达式不适合写复杂业务逻辑
虽然表达式有一定计算能力,但模板的职责应该是清晰描述界面。
不要把大量复杂逻辑都塞进:
html
{{ ......一长串复杂处理...... }}
后续复杂派生数据通常应交给计算属性等机制处理。
{``{ }} 默认按普通文本输出
如果数据值是:
js
message: '<strong>Hello</strong>'
写:
html
<div>{{ message }}</div>
页面不会把 <strong> 当作真正标签执行,而会把它按文本处理。
这是一个重要的安全默认行为。
Vue 另外提供 v-html 输出真实 HTML,但它对不可信内容存在 XSS 风险,因此本阶段不要把 v-html 当普通文本渲染方案使用。
第八部分:v-for------根据数据批量生成界面
二十一、最基本语法
html
<li v-for="item in items">
{{ item }}
</li>
v-for 是 Vue 内置指令。
最常见语法:
text
别名 in 数据源
例如:
html
<li v-for="name in names">
{{ name }}
</li>
其中:
names:被遍历的数据源;name:当前这一次循环对应的元素别名。
假设:
js
names: ['Java', 'Vue', 'MySQL']
Vue 会理解为:
text
第一次:name = 'Java'
第二次:name = 'Vue'
第三次:name = 'MySQL'
于是生成三个 <li>。
二十二、v-for 获取索引
语法:
html
<li v-for="(item, index) in items">
{{ index }} - {{ item }}
</li>
其中:
- 第一个参数
item:当前元素; - 第二个参数
index:当前索引; - 数组索引从
0开始。
例如:
js
items: ['A', 'B', 'C']
结果类似:
text
0 - A
1 - B
2 - C
如果页面希望从 1 开始展示序号,可以写:
html
<li v-for="(item, index) in items">
{{ index + 1 }}. {{ item }}
</li>
二十三、对象数组才是实际开发中更常见的数据形态
真实业务很少只有纯字符串数组。更常见的是:
js
courses: [
{ id: 101, name: 'Java' },
{ id: 102, name: 'Vue 3' },
{ id: 103, name: 'Spring Boot' }
]
模板:
html
<ul>
<!--
course 表示当前课程对象。
:key="course.id" 使用每条数据稳定且唯一的 id 作为节点标识。
-->
<li v-for="course in courses" :key="course.id">
{{ course.id }} - {{ course.name }}
</li>
</ul>
每轮中的 course 都是一个对象,因此读取属性:
html
{{ course.id }}
{{ course.name }}
二十四、为什么 v-for 经常要写 :key
推荐写法:
html
<li v-for="course in courses" :key="course.id">
{{ course.name }}
</li>
key 可以帮助 Vue 识别列表中每个节点的身份。
当列表发生插入、删除、排序时,Vue 不只是看到"这里有几个 <li>",还可以借助 key 判断:
- 哪一项还是原来的那一项;
- 哪一项被删除;
- 哪一项新增;
- 哪一项位置发生变化。
key 应该使用什么值
通常使用业务数据中稳定、唯一的基础类型 ID:
html
:key="item.id"
理想值:
- number;
- string。
不要用整个对象作为 key。
为什么不是所有场景都推荐 index
如果列表会增加、删除、排序,索引会随着位置改变,不一定能稳定表示"这条业务数据是谁"。
所以有真实唯一 ID 时,优先:
html
:key="item.id"
而不是:
html
:key="index"
第九部分:数据响应式------Vue 最核心的体验之一
二十五、什么叫"响应式"
先看一个结果:
js
this.count++
执行后:
html
<p>{{ count }}</p>
页面显示的数字会跟着改变。
再看数组:
js
this.courses.push({
id: 104,
name: 'MySQL'
})
如果模板是:
html
<li v-for="course in courses" :key="course.id">
{{ course.name }}
</li>
页面会自动多出一项。
这里并没有手动调用:
js
document.createElement(...)
也没有手动调用:
js
element.textContent = ...
这就是最直观的响应式体验:
状态发生变化后,依赖这份状态的界面会自动重新计算并更新。
二十六、响应式不是"数据自己变了"
"响应式"并不是说 Vue 会随便修改你的数据。
仍然是开发者主动修改数据:
js
this.count++
Vue 自动做的是后半段:
text
发现响应式状态发生变化
↓
找出哪些渲染内容依赖它
↓
重新计算必要的渲染结果
↓
以尽可能少的 DOM 操作更新页面
因此最准确的理解应该是:
我们负责修改状态,Vue 负责让视图对状态变化作出响应。
二十七、一个完整的响应式计数器
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>Vue 响应式计数器</title>
</head>
<body>
<div id="app">
<!--
双大括号读取 data() 返回对象中的 count。
count 每次发生响应式变化后,这里的文本会自动更新。
-->
<p>当前计数:{{ count }}</p>
<!--
@click 是 v-on:click 的简写。
点击时执行 increment 方法。
模板里写方法名时,不需要这里手工传入 event。
-->
<button @click="increment">加 1</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue
createApp({
data() {
// data() 返回的普通对象会成为组件的响应式状态来源。
return {
count: 0
}
},
methods: {
// increment 是一个组件方法。
// 在 methods 中使用普通方法语法时,this 会绑定到当前组件实例。
increment() {
// this.count 访问 data() 中声明的 count。
// ++ 会把 count 从当前数值加 1。
// 状态变化后,Vue 会自动安排依赖 count 的视图更新。
this.count++
}
}
}).mount('#app')
</script>
</body>
</html>
第十部分:methods------把交互逻辑写成组件方法
二十八、methods 是什么
选项式 API 中,methods 用来声明组件方法。
基本结构:
js
methods: {
add() {
// 方法逻辑
},
remove() {
// 方法逻辑
}
}
官方类型可以概括为:
ts
methods?: {
[key: string]: (this: ComponentPublicInstance, ...args: any[]) => any
}
这表示:
methods是一个对象;- 对象的 key 是方法名;
- 每个方法都可以接收参数;
- 返回值类型取决于方法内部
return的内容; - Vue 会把普通方法的
this绑定到组件实例。
方法参数
你可以自己定义参数:
js
methods: {
addNumber(num) {
this.count += num
}
}
模板调用:
html
<button @click="addNumber(5)">加 5</button>
此时 num 接收到 5。
方法返回值
方法可以没有返回值:
js
add() {
this.count++
}
这种情况下函数实际返回 undefined。
也可以显式返回:
js
getDouble() {
return this.count * 2
}
那么返回值就是计算后的数字。
不过用于事件处理的方法,很多时候主要职责是修改状态,不需要依赖返回值。
methods 中不推荐箭头函数
不要写:
js
methods: {
add: () => {
this.count++
}
}
因为箭头函数不会得到 Vue 自动绑定的组件实例 this。
推荐:
js
methods: {
add() {
this.count++
}
}
第十一部分:@click 与事件处理
二十九、v-on 与 @ 的关系
完整写法:
html
<button v-on:click="increment">加 1</button>
简写:
html
<button @click="increment">加 1</button>
两者含义相同。
click 是浏览器原生点击事件。
increment 是组件中 methods 声明的方法。
三十、方法事件处理器会自动收到原生事件对象
例如:
js
methods: {
handleClick(event) {
console.log(event.target)
}
}
模板:
html
<button @click="handleClick">点击</button>
当点击发生时,event 会收到浏览器原生事件对象。
自定义参数与 $event
如果既要传自己的参数,又要事件对象:
html
<button @click="showCourse(course, $event)">
查看
</button>
方法:
js
methods: {
showCourse(course, event) {
console.log(course)
console.log(event.target)
}
}
其中 $event 是模板里用于表示当前原生 DOM 事件的特殊变量。
本阶段记住最常用的两种形式即可:
html
@click="methodName"
和:
html
@click="methodName(value)"
第十二部分:把 v-for、methods、响应式组合起来
三十一、完整课程列表案例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue 3 列表响应式案例</title>
</head>
<body>
<div id="app">
<h2>{{ title }}</h2>
<ul>
<!--
v-for="course in courses"
表示遍历 courses 数组。
每一轮循环中,course 都代表当前课程对象。
:key="course.id"
使用课程稳定且唯一的 id 作为当前列表节点的身份标识。
-->
<li v-for="course in courses" :key="course.id">
{{ course.id }} - {{ course.name }}
</li>
</ul>
<!--
点击按钮时调用 addCourse。
addCourse 会向响应式数组 courses 中 push 一条新对象。
Vue 检测到数组状态变化后,会自动更新上面的 v-for 列表。
-->
<button @click="addCourse">添加一门课程</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue
const app = createApp({
data() {
return {
// 普通字符串状态,用插值表达式显示在 h2 中。
title: '课程列表',
// 对象数组状态。
// 数组以及其中对象的状态变化都可以参与 Vue 的响应式更新。
courses: [
{ id: 101, name: 'Java' },
{ id: 102, name: 'Vue 3' },
{ id: 103, name: 'Spring Boot' }
],
// 用于生成下一条课程的 id。
nextId: 104
}
},
methods: {
addCourse() {
// push() 是 JavaScript 数组的原生方法。
// 参数:要添加到数组末尾的一个或多个元素。
// 返回值:数组修改后的新长度。
const newLength = this.courses.push({
id: this.nextId,
name: `课程 ${this.nextId}`
})
// nextId 自增,保证下一次添加时使用新的 id。
this.nextId++
// 这里只是为了观察 push() 的返回值。
// 它不是让 Vue 更新页面所必需的代码。
console.log('添加后的数组长度:', newLength)
}
}
})
app.mount('#app')
</script>
</body>
</html>
这个案例把本章几乎所有核心概念串在一起:
text
data() 提供响应式状态
↓
模板通过 {{ }} 和 v-for 读取状态
↓
用户触发 click
↓
methods 修改状态
↓
Vue 检测到状态变化
↓
自动更新依赖这份状态的 DOM
只要把这条链理解透,Vue 的第一道门就真正打开了。
第十三部分:数组方法与响应式列表更新
三十二、哪些数组操作常用于修改列表
Vue 的响应式数组仍然是按照 JavaScript 数组的使用方式操作。
push(...items)
作用:在数组末尾添加一个或多个元素。
示例:
js
this.courses.push({ id: 104, name: 'MySQL' })
参数:
- 可以传一个元素;
- 也可以传多个元素。
返回值:
- 修改后的数组新长度,类型为 number。
例如:
js
const len = this.courses.push(newCourse)
如果添加后数组有 4 项,len 就是 4。
pop()
作用:删除数组最后一个元素。
js
const removed = this.courses.pop()
参数:无。
返回值:
- 返回被删除的最后一个元素;
- 如果数组原本为空,则返回
undefined。
shift()
作用:删除数组第一个元素。
js
const removed = this.courses.shift()
参数:无。
返回值:
- 被删除的第一个元素;
- 空数组时返回
undefined。
unshift(...items)
作用:向数组开头添加一个或多个元素。
js
const len = this.courses.unshift({ id: 100, name: 'HTML' })
返回值:
- 修改后的数组新长度。
splice(start, deleteCount, ...items)
作用:从数组指定位置删除、替换或插入元素。
示例:删除索引 1 的一项:
js
this.courses.splice(1, 1)
参数:
start:从哪个索引开始;deleteCount:删除多少项;...items:可选,要插入的新元素。
返回值:
- 一个新数组,包含被删除的元素。
例如:
js
const removed = this.courses.splice(1, 1)
如果原来索引 1 是 { id: 102, name: 'Vue 3' },那么 removed 会类似:
js
[
{ id: 102, name: 'Vue 3' }
]
sort() 与 reverse()
这两个方法会直接修改原数组。
如果响应式数组发生排序,Vue 会根据更新后的顺序重新处理列表渲染。
但如果是在后续的计算属性中做"派生排序",通常更推荐先复制数组,再排序,避免意外修改原始状态:
js
const sorted = [...this.courses].sort(...)
这一点在进入计算属性时会更重要。
第十四部分:为什么修改数组后 Vue 能知道
三十三、从概念上理解依赖追踪
当前阶段不需要去阅读 Vue 响应式系统的底层源码,但至少要理解三个词:
1. 响应式状态
例如:
js
count
courses
message
这些状态由 Vue 管理。
2. 依赖
如果模板写:
html
<p>{{ count }}</p>
这段渲染就依赖 count。
如果另一个区域完全没有使用 count,那么它并不因为 count 改变就必须全部重做。
3. 触发更新
当:
js
this.count++
Vue 能够得知响应式状态发生变化,然后安排相关渲染更新。
所以响应式可以概括成:
text
读取时建立依赖关系
修改时触发相关更新
不需要把它理解成"每次数据变了就粗暴刷新整个网页"。
Vue 的渲染系统会尽量只执行必要的组件更新和 DOM 操作。
第十五部分:Vue 3 的 ref() 与 reactive() 应该怎样理解
三十四、为什么在 data() 之外还会看到 ref() 和 reactive()
Vue 3 同时支持选项式 API 和组合式 API。
本章前面的 CDN 例子主要使用选项式 API:
js
data() {
return {
count: 0
}
}
在组合式 API 中,常见响应式入口是:
js
ref()
reactive()
这两套风格都属于 Vue 3。
不需要理解成"一个正确、一个错误"。Vue 官方文档明确支持两种 API 风格。
在当前阶段把 data() 用熟,再理解 ref() / reactive() 的基本作用即可。
三十五、ref()
基本写法:
js
const { ref } = Vue
const count = ref(0)
官方类型:
ts
function ref<T>(value: T): Ref<UnwrapRef<T>>
可以先简化理解成:
ref(value)接收一个值,返回一个带响应式.value属性的 ref 对象。
参数
value:希望包装成响应式状态的初始值。
可以是:
- number;
- string;
- boolean;
- object;
- array 等。
例如:
js
const count = ref(0)
const message = ref('Hello')
const list = ref([])
返回值
返回 ref 对象。
最核心结构可以理解为:
js
{
value: 0
}
当然真实实现不是这样一个简单普通对象,但从使用方式上,关键就是 .value。
JavaScript 中读取和修改
js
console.log(count.value)
count.value++
模板中为什么不写 .value
如果 ref 被暴露给模板,模板顶层使用时通常可以自动解包:
html
<p>{{ count }}</p>
而不是:
html
<p>{{ count.value }}</p>
这一点是初次接触组合式 API 时最容易混淆的地方之一。
三十六、reactive()
基本写法:
js
const { reactive } = Vue
const state = reactive({
count: 0,
message: 'Hello'
})
官方类型:
ts
function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
可以先简化理解:
reactive()接收对象类型数据,返回这个对象的深层响应式代理。
参数
target:对象类型目标。
常见:
js
reactive({ ... })
reactive([ ... ])
返回值
返回一个响应式代理对象。
Vue 3 的响应式对象底层会使用 JavaScript Proxy 机制进行代理。
使用时像普通对象一样:
js
state.count++
state.message = 'Vue 3'
不需要 .value。
ref() 与 reactive() 最基础的区别
js
const count = ref(0)
count.value++
而:
js
const state = reactive({ count: 0 })
state.count++
现阶段先记住使用形态即可,不需要在第一课里追求复杂的解构、浅层响应式等内容。
第十六部分:用 CDN 写一个组合式 API 版本 Hello World
三十七、setup() + ref() 基础示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>Vue 3 ref Demo</title>
</head>
<body>
<div id="app">
<!--
message 是 setup() 返回给模板的 ref。
模板中会自动解包,因此直接写 message,不写 message.value。
-->
<p>{{ message }}</p>
<!-- 点击后执行 changeMessage -->
<button @click="changeMessage">修改文本</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp, ref } = Vue
createApp({
setup() {
// ref('Hello Vue'):
// 参数是初始值 'Hello Vue';
// 返回一个响应式 ref 对象;
// 在 JavaScript 中通过 message.value 访问内部值。
const message = ref('Hello Vue')
function changeMessage() {
// JavaScript 中修改 ref,需要写 .value。
// 修改后,模板中依赖 message 的内容会自动更新。
message.value = 'Vue 3 响应式更新成功'
}
// setup() 返回对象中的属性,会暴露给当前模板使用。
return {
message,
changeMessage
}
}
}).mount('#app')
</script>
</body>
</html>
这一段的目的不是立刻切换到组合式 API,而是提前建立一个正确印象:
text
Options API 常见状态入口:data()
Composition API 常见状态入口:ref() / reactive()
两者都属于 Vue 3。
第十七部分:DOM 更新时机与 nextTick()
三十八、为什么数据已经改了,立刻读取 DOM 可能还是旧值
Vue 修改响应式状态后,DOM 更新并不是每一行赋值都立即同步执行一次。
Vue 会对状态变化进行缓冲,并在"next tick"更新周期中统一处理必要更新,从而避免同一个组件在短时间内被重复刷新很多次。
例如:
js
this.count++
从"状态"的角度看,this.count 已经改变。
但如果下一行马上手动读取某个 DOM 文本,有可能 DOM 还没有完成这一轮 Vue 更新。
这时可以使用 nextTick() 等待 DOM 更新完成。
选项式 API 中的 $nextTick()
js
methods: {
async increment() {
this.count++
await this.$nextTick()
// 执行到这里时,当前这轮 DOM 更新已经完成。
console.log('DOM 已更新')
}
}
this.$nextTick() 的返回值是:
ts
Promise<void>
因此可以使用:
js
await this.$nextTick()
注意:普通的响应式页面更新并不要求每次都手工 nextTick()。
只有当代码确实需要"等 DOM 更新后再做某件事"时才使用它。
第十八部分:声明式渲染与响应式的关系
三十九、这两个概念不是一回事,但紧密配合
声明式渲染回答的是:
界面和数据之间是什么关系?
例如:
html
<p>{{ message }}</p>
表示这个段落显示 message。
html
<li v-for="item in items">{{ item }}</li>
表示根据 items 生成列表。
响应式回答的是:
当数据变化以后,依赖它的界面怎么办?
Vue 的答案是:自动更新。
因此两者连起来:
text
用模板声明"数据应该怎样显示"
↓
Vue 建立数据与渲染之间的依赖关系
↓
响应式数据发生变化
↓
Vue 自动让视图重新符合这份声明
这就是 Vue 开发"爽"的最核心来源之一。
第十九部分:一个更完整、但仍不超出本章范围的练习
四十、课程管理小案例
目标:
- 显示标题;
- 根据数组渲染课程;
- 显示课程数量;
- 点击按钮添加课程;
- 点击每条课程旁边的按钮删除课程;
- 全程不手工操作 DOM。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue 3 课程列表</title>
</head>
<body>
<div id="app">
<!-- title 来自 data() -->
<h1>{{ title }}</h1>
<!--
courses.length 是 JavaScript 数组的 length 属性。
当 courses 增删后,长度变化会反映到这里。
-->
<p>当前共有 {{ courses.length }} 门课程</p>
<ul>
<li
v-for="(course, index) in courses"
:key="course.id"
>
<!-- index 从 0 开始,因此显示序号时加 1。 -->
{{ index + 1 }}.
{{ course.name }}
<!--
把当前课程的 id 作为参数传给 removeCourse。
这里调用的是组件 methods 中的方法。
-->
<button @click="removeCourse(course.id)">
删除
</button>
</li>
</ul>
<button @click="addCourse">
添加课程
</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue
createApp({
data() {
return {
// 页面标题。
title: 'Vue 3 课程管理',
// 响应式课程数组。
courses: [
{ id: 1, name: 'HTML' },
{ id: 2, name: 'JavaScript' },
{ id: 3, name: 'Vue 3' }
],
// 下一条记录使用的唯一 id。
nextId: 4
}
},
methods: {
addCourse() {
// 创建一条新的普通对象。
// 只要它被放入响应式数组,后续相关变化就能参与 Vue 的响应式系统。
const newCourse = {
id: this.nextId,
name: `课程 ${this.nextId}`
}
// push(newCourse):
// 参数是要加入数组末尾的新课程对象;
// 返回值是加入后数组的新长度。
this.courses.push(newCourse)
// 为下一条课程准备新的 id。
this.nextId++
},
removeCourse(id) {
// 参数 id 来自模板:removeCourse(course.id)。
// 它代表用户希望删除的那条课程 id。
// findIndex(callback) 会从前向后查找第一个满足条件的元素。
// 回调 course => course.id === id:
// 对每一项判断课程 id 是否等于要删除的 id。
//
// 返回值:
// 找到时返回索引,例如 0、1、2;
// 找不到时返回 -1。
const index = this.courses.findIndex(
(course) => course.id === id
)
// 一定先判断 index !== -1。
// 因为 splice(-1, 1) 会从末尾位置处理,可能误删最后一条。
if (index !== -1) {
// splice(index, 1):
// 从 index 开始删除 1 项。
// 返回值是由被删除元素组成的新数组。
this.courses.splice(index, 1)
}
}
}
}).mount('#app')
</script>
</body>
</html>
这个案例最值得观察什么
不是"按钮能不能点",而是整个程序里没有任何:
js
document.querySelector(...)
document.createElement(...)
appendChild(...)
removeChild(...)
你只是在修改:
js
this.courses
页面变化由 Vue 根据模板声明自动完成。
这就是把"界面控制中心"从 DOM 操作转移到状态管理。
第二十部分:原生 JavaScript 与 Vue 的思维对比
四十一、原生 DOM 模式
大致思路:
text
数据改变
↓
自己找到对应 DOM
↓
自己决定新增 / 删除 / 修改哪个节点
↓
自己保持 DOM 与数据同步
优点:
- 没有框架依赖;
- 对浏览器原理理解直接;
- 很小的交互非常合适。
缺点:
- 状态多以后 DOM 同步代码会明显增多;
- 业务逻辑和 DOM 操作容易混在一起;
- 页面规模变大以后维护成本上升。
四十二、Vue 模式
大致思路:
text
声明"状态应该如何渲染"
↓
用户交互时修改状态
↓
Vue 自动更新视图
开发者主要关心:
text
我的数据是什么?
用户操作以后数据应该变成什么?
不同数据状态下界面应该如何显示?
而不是每次都关心:
text
我要找到哪个 DOM?
我要 append 还是 remove?
文本要改哪个节点?
第二十一部分:常见错误与排查方式
四十三、错误一:忘记引入 Vue
如果直接写:
js
const { createApp } = Vue
但前面没有成功加载 Vue 脚本,浏览器会报类似:
text
Vue is not defined
检查:
html
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
是否在自己的业务脚本之前加载。
四十四、错误二:mount() 选择器写错
HTML:
html
<div id="app"></div>
却写:
js
app.mount('#root')
Vue 找不到正确挂载目标。
两边应一致:
js
app.mount('#app')
四十五、错误三:data() 忘记 return
错误:
js
data() {
const count = 0
}
这样没有把状态对象返回给 Vue。
正确:
js
data() {
return {
count: 0
}
}
四十六、错误四:模板使用了不存在的数据名
data():
js
return {
message: 'Hello'
}
模板却写:
html
{{ msg }}
msg 并没有被定义。
应统一名称:
html
{{ message }}
四十七、错误五:methods 里把需要 this 的方法写成箭头函数
不推荐:
js
methods: {
add: () => {
this.count++
}
}
正确:
js
methods: {
add() {
this.count++
}
}
四十八、错误六:组合式 API 的 ref 在 JavaScript 中忘写 .value
js
const count = ref(0)
JavaScript 中:
js
count.value++
而不是:
js
count++
模板顶层使用时才通常自动解包:
html
{{ count }}
四十九、错误七:v-for 的数据源不是你以为的那个变量
例如:
html
<li v-for="item in list">
{{ item }}
</li>
但 data() 中写的是:
js
return {
items: ['A', 'B']
}
模板应该写:
html
<li v-for="item in items">
{{ item }}
</li>
Vue 不会自动猜测变量名。
五十、错误八:把 CDN 教学方式误认为企业项目最终形态
CDN 写法非常适合理解:
createApp();mount();- 模板;
data();- 指令;
- 响应式。
但完整项目应该优先使用当前官方推荐的 Vite 工程化方式。
正确学习顺序是:
text
先用最简单环境看懂 Vue 的核心运行逻辑
↓
再进入 Vite 工程学习 .vue 单文件组件与工程结构
而不是一开始就让大量配置文件掩盖最核心的概念。
第二十二部分:几个必须真正说清楚的概念
五十一、Vue 是不是"替代 JavaScript"
不是。
Vue 本身就是 JavaScript 生态中的框架。
你在 Vue 里仍然大量使用:
- 变量;
- 对象;
- 数组;
- 函数;
- 条件判断;
- 数组方法;
- 模块化;
- Promise;
- 异步编程。
Vue 提供的是一套更适合组织用户界面的规则。
JavaScript 基础越扎实,Vue 越容易学得稳。
五十二、Vue 是不是"完全不操作 DOM"
Vue 内部当然最终还是要让浏览器 DOM 发生变化,否则页面不可能更新。
真正的区别是:
大部分常规业务界面更新不再由开发者亲自写 DOM 操作,而由 Vue 根据声明式模板和响应式状态自动处理。
某些特殊场景仍可能需要直接访问 DOM,Vue 也提供模板引用等机制。但那不是基础业务渲染的默认方式。
五十三、响应式是不是"自动调用接口"
不是。
响应式解决的是:状态变化以后,依赖它的逻辑和界面怎样自动更新。
至于数据从哪里来,可能是:
- 用户点击;
- 表单输入;
- 定时器;
- 后端接口;
- 本地存储;
- WebSocket。
接口请求与响应式不是同一个概念。
接口把新数据拿回来以后,再把数据赋给响应式状态,Vue 才会根据状态变化更新页面。
五十四、声明式是不是"不写逻辑"
不是。
声明式只是说:对于界面结果,尽量描述"是什么",而不是把每一步 DOM 操作全部手工写出来。
业务逻辑仍然存在。例如:
js
removeCourse(id) {
const index = this.courses.findIndex(
course => course.id === id
)
if (index !== -1) {
this.courses.splice(index, 1)
}
}
逻辑仍然由你编写。
只是最后"把对应 <li> 从 DOM 中删掉"这一步不需要你自己操作 DOM。
第二十三部分:知识点速查表
五十五、createApp()
作用:创建 Vue 应用实例。
常用写法:
js
const app = createApp(rootComponent)
核心参数:
rootComponent:根组件,必传;rootProps:传给根组件的 props,可选。
返回值:
- Vue 应用实例
App。
五十六、app.mount()
作用:把 Vue 应用挂载到页面容器。
写法:
js
app.mount('#app')
参数:
- CSS 选择器字符串;或
- 实际 DOM 元素。
返回值:
- 根组件公开实例。
注意:
- 一般在应用配置完成后最后调用。
五十七、data()
作用:选项式 API 中声明组件初始响应式状态。
写法:
js
data() {
return {
count: 0
}
}
参数:
- 基础使用时通常不主动声明参数。
返回值:
- 普通 JavaScript 对象;Vue 将其转换为响应式状态。
注意:
- 必须记得
return; - 推荐普通方法写法;
- 状态名应清晰;
- 计划使用的顶层状态建议提前声明。
五十八、methods
作用:声明组件方法。
写法:
js
methods: {
increment() {
this.count++
}
}
参数:
- 每个方法根据业务自行定义;
- 直接作为 DOM 事件处理器时可收到原生事件对象。
返回值:
- 由方法内部
return决定; - 没有显式
return时为undefined。
注意:
- 需要组件
this时不要使用箭头函数。
五十九、{``{ }}
作用:文本插值。
写法:
html
{{ message }}
可放:
- 数据属性;
- 简单 JavaScript 表达式。
特点:
- 默认按文本输出;
- 数据变化后自动更新显示。
六十、v-for
作用:基于数据源重复渲染元素或模板块。
数组常用语法:
html
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
带索引:
html
<li v-for="(item, index) in items" :key="item.id">
{{ index + 1 }} - {{ item.name }}
</li>
数据源可以包括数组、对象、数字、字符串及其他可迭代数据。
实际业务中最常见的是数组和对象数组。
六十一、ref()
作用:创建一个可变的响应式 ref。
写法:
js
const count = ref(0)
参数:
- 初始值。
返回值:
- ref 对象。
JavaScript 访问:
js
count.value
模板顶层访问通常:
html
{{ count }}
六十二、reactive()
作用:返回对象的深层响应式代理。
写法:
js
const state = reactive({
count: 0
})
参数:
- 对象类型目标。
返回值:
- 响应式代理对象。
使用:
js
state.count++
六十三、this.$nextTick()
作用:等待当前响应式状态变化对应的 DOM 更新完成。
常用写法:
js
await this.$nextTick()
可选参数:
- callback 回调函数。
返回值:
ts
Promise<void>
注意:
- 不是每次修改状态都必须调用;
- 只有后续逻辑必须依赖"更新后的 DOM"时才使用。
第二十四部分:环境选择的最终建议
六十四、什么时候用 CDN
适合:
- 学基础语法;
- 验证一个简单想法;
- 对已有 HTML 做局部增强;
- 不希望引入构建步骤的小模块。
典型:
html
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
六十五、什么时候用 Vite
适合:
- 新的正式 Vue 项目;
.vue单文件组件;- 组件化开发;
- 路由;
- Pinia;
- UI 组件库;
- npm 依赖;
- 打包构建;
- 生产部署。
当前推荐入口:
bash
npm create vue@latest
第二十五部分:本章最重要的完整认知链
六十六、从数据到界面到底发生了什么
以这段代码为例:
html
<div id="app">
<p>{{ message }}</p>
<button @click="changeMessage">修改</button>
</div>
js
createApp({
data() {
return {
message: 'Hello'
}
},
methods: {
changeMessage() {
this.message = 'Vue 3'
}
}
}).mount('#app')
运行过程可以拆成:
第一步:Vue 被加载
浏览器获得 Vue 运行时能力。
第二步:createApp() 创建应用
Vue 得到根组件配置,知道这个组件有哪些数据和方法。
第三步:mount('#app')
Vue 接管对应容器并处理模板。
第四步:模板读取 message
html
{{ message }}
Vue 建立渲染与响应式状态之间的关系。
第五步:点击按钮
html
@click="changeMessage"
触发组件方法。
第六步:方法修改状态
js
this.message = 'Vue 3'
第七步:Vue 安排更新
Vue 发现 message 发生变化。
第八步:相关界面被更新
<p> 中的文本从:
text
Hello
变为:
text
Vue 3
全过程里,业务代码没有直接寻找 <p>。
这就是声明式渲染与数据响应式配合后的完整体验。
第二十六部分:最后总结
六十七、为什么学 Vue
因为现代前端开发面对的是大量"状态驱动界面"的问题。Vue 通过声明式模板、响应式系统以及后续的组件化生态,减少重复 DOM 同步代码,让开发者更集中地处理数据与业务逻辑。
六十八、Vue 是什么
Vue 是用于构建 Web 用户界面的渐进式 JavaScript 框架。
最核心的理解不是背定义,而是:
用数据描述状态,用模板描述界面,让 Vue 负责把状态和界面保持同步。
六十九、什么是渐进式
可以按项目规模逐步采用 Vue:
text
局部页面增强
↓
更多 Vue 交互
↓
组件化
↓
Vite 工程化
↓
Router / Pinia / UI 组件库
↓
完整应用
不需要"先把所有 Vue API 学完才能开发"。
七十、什么是声明式渲染
不是手工描述每一步 DOM 操作,而是声明:
text
数据是什么时,界面应该是什么样子
例如:
html
{{ message }}
和:
html
<li v-for="item in items">{{ item }}</li>
七十一、什么是数据响应式
开发者修改状态:
js
this.count++
Vue 自动让依赖这份状态的页面内容更新。
核心关系:
text
状态变化 → 相关视图自动响应
七十二、最应该牢牢记住的第一套 Vue 3 代码骨架
html
<div id="app">
<!-- 模板在这里读取响应式数据、绑定事件、使用指令 -->
<p>{{ message }}</p>
<button @click="changeMessage">修改</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue
createApp({
data() {
return {
// data() 返回对象中的属性成为响应式状态。
message: 'Hello Vue 3'
}
},
methods: {
changeMessage() {
// 修改状态,而不是手工修改 DOM。
// Vue 会根据响应式系统自动更新模板中依赖 message 的位置。
this.message = 'Vue 3 声明式渲染'
}
}
}).mount('#app')
</script>
如果能完整解释这段代码中的每一句:
Vue从哪里来;createApp()做什么;- 参数是什么;
- 返回什么;
data()为什么返回对象;message为什么能直接在模板使用;@click是什么;methods里的this指向谁;this.message变化以后为什么页面会变化;mount('#app')为什么必须存在;
那么"Vue 3 背景介绍 + 声明式渲染 + 数据响应式"这一阶段就不再只是记住几个语法,而是已经形成了一套完整理解。
参考资料与当前版本说明
本文内容按 2026 年 8 月可查到的 Vue 3 官方文档与 VS Code 官方资料整理,涉及版本与工具链结论以以下官方页面为准:
- Vue 官方中文站:https://cn.vuejs.org/
- Vue 快速上手:https://cn.vuejs.org/guide/quick-start
- 创建一个 Vue 应用:https://cn.vuejs.org/guide/essentials/application
- 模板语法:https://cn.vuejs.org/guide/essentials/template-syntax
- 响应式基础:https://cn.vuejs.org/guide/essentials/reactivity-fundamentals
- 响应式 API:核心:https://cn.vuejs.org/api/reactivity-core
- 列表渲染:https://cn.vuejs.org/guide/essentials/list
- 事件处理:https://cn.vuejs.org/guide/essentials/event-handling
- 状态选项:https://cn.vuejs.org/api/options-state
- Vue 工具链:https://cn.vuejs.org/guide/scaling-up/tooling
- Vue 官方 FAQ:https://vuejs.org/about/faq
- Vue - Official VS Code 扩展:https://marketplace.visualstudio.com/items?itemName=Vue.volar
- VS Code HTML 支持:https://code.visualstudio.com/docs/languages/html
其中有几条当前开发时尤其值得记住:
- Vue 3 是当前 Vue 主版本;
- Vue 2 已于 2023 年 12 月 31 日结束官方支持;
- Vue 官方当前建议新项目使用
create-vue创建 Vite 项目; - Vue CLI 已处于维护模式,不应再作为新项目默认脚手架;
- VS Code 当前推荐 Vue 插件为 Vue - Official(原 Volar);
- VS Code 已内置 HTML 自动闭合标签与 linked editing 等能力,因此部分旧的标签辅助插件不再是必需品。