对于 Vue 3:从为什么学 Vue,到声明式渲染与数据响应式的解析

本章围绕三个核心问题展开:Vue 为什么值得学、Vue 如何把数据渲染成界面、数据变化后界面为什么会自动变化。

学完这一章,应该能够不依赖工程化脚手架,在一个普通 HTML 文件中完成 Vue 3 的基本运行;能够说清楚 createApp()mount()data()methods、插值表达式、v-forkey、事件处理以及响应式数据之间的关系;同时能够理解传统 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>

这段代码没有错误,而且在小页面中完全可以工作。但注意它的思维过程:

  1. 找到容器;
  2. 遍历数组;
  3. 创建元素;
  4. 设置文本;
  5. 插入元素。

这就是典型的命令式编程:开发者不仅要说"结果是什么",还要亲自告诉程序"为了得到这个结果,每一步应该怎么操作"。

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 语法看起来通常不难,真正的难点是:看懂不等于会写,会写不等于能独立解决问题。

更有效的方法是:

  1. 每个语法先理解"它解决什么问题";
  2. 自己从空文件写一遍,不只复制;
  3. 主动改变数据观察结果;
  4. 主动写错,再看控制台错误;
  5. 用一个小案例同时组合两三个知识点;
  6. 遇到不确定的 API 优先查官方文档;
  7. 把容易混淆的点自己总结出来。

例如学完 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() 的参数

可以传:

  1. CSS 选择器字符串;
  2. 实际 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-formethods、响应式组合起来

三十一、完整课程列表案例

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 官方资料整理,涉及版本与工具链结论以以下官方页面为准:

  1. Vue 官方中文站:https://cn.vuejs.org/
  2. Vue 快速上手:https://cn.vuejs.org/guide/quick-start
  3. 创建一个 Vue 应用:https://cn.vuejs.org/guide/essentials/application
  4. 模板语法:https://cn.vuejs.org/guide/essentials/template-syntax
  5. 响应式基础:https://cn.vuejs.org/guide/essentials/reactivity-fundamentals
  6. 响应式 API:核心:https://cn.vuejs.org/api/reactivity-core
  7. 列表渲染:https://cn.vuejs.org/guide/essentials/list
  8. 事件处理:https://cn.vuejs.org/guide/essentials/event-handling
  9. 状态选项:https://cn.vuejs.org/api/options-state
  10. Vue 工具链:https://cn.vuejs.org/guide/scaling-up/tooling
  11. Vue 官方 FAQ:https://vuejs.org/about/faq
  12. Vue - Official VS Code 扩展:https://marketplace.visualstudio.com/items?itemName=Vue.volar
  13. 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 等能力,因此部分旧的标签辅助插件不再是必需品。
相关推荐
粥里有勺糖1 小时前
视野修炼-技术周刊第132期 | 一些有趣的组件
前端·github·aigc
এ慕ོ冬℘゜1 小时前
样式控制 .css ()
前端·css
leoZ2312 小时前
第 6 篇:SchemaForm 渲染器核心实现
前端·javascript·vue.js·人工智能·神经网络·机器学习·自然语言处理
CoderYanger3 小时前
前端基础——JavaScript(基础语法)(下篇)
java·开发语言·前端·javascript·程序人生·面试·职场和发展
还是大剑师兰特3 小时前
vue项目浏览器版本判别,低于IE11跳转到新页面
前端·javascript·vue.js
计算机魔术师3 小时前
奥尔特曼致歉 GPT-6 Astra 发布混乱,现已面向所有 Plus / Pro 等用户推出
前端
落樱弥城3 小时前
RHI 设计考量:不同 API 差异分析
服务器·前端·性能优化
热心网友俣先生3 小时前
2026国赛C题:五年命题规律与预测
java·c语言·前端·数学建模
想吃火锅10053 小时前
【leetcode】42.接雨水js
开发语言·javascript·ecmascript