引路者👇

一、Vue 框架初相识

1.1 Vue 是什么

1.2 渐进式框架的魅力

1.3 单文件组件探秘

二、搭建首个 Vue 项目

2.1 环境搭建先行

2.2 项目创建进行时

2.3 切换至项目目录

2.4 安装项目依赖包

2.5 启动项目之旅

2.6 见证创建成功

三、深入剖析项目结构

四、基础样式与内容处理

4.1 查看初始页面模样

4.2 清除基础样式与内容

4.3 审视删除后的页面

五、选项式 API 与组合式 API 辨析

5.1 基本概念解读

5.1.1 两者定义阐述

5.1.2 两者区别剖析

5.1.3 是否都要学习

六、Vue3 基础语法详解

6.1 模板语法

6.1.1 文本插值

6.1.2 原始 HTML

6.1.3 属性绑定

6.2 Class 与 Style 绑定

6.2.1 Class 绑定

6.2.2 Style 绑定

6.3 条件渲染

6.3.1 v-if / v-else-if / v-else

6.3.2 v-show

6.3.3 v-if vs v-show 核心区别

6.4 列表渲染

6.4.1 v-for 基本用法

6.4.2 key 属性的重要性

6.5 事件处理

6.5.1 基本事件监听

6.5.2 事件修饰符

6.5.3 按键修饰符

6.5.4 常用事件类型汇总

6.6 表单输入绑定

6.6.1 基础用法

6.6.2 v-model 修饰符

6.6.3 组合式 API 完整示例

七、Vue3 核心语法进阶

7.1 computed 计算属性

7.1.1 基本概念

7.1.2 只读计算属性

7.1.3 可读写计算属性

7.2 watch 监听器

7.2.1 基本概念

7.2.2 五种常见使用场景

场景一:监视 ref 定义的基本类型数据

场景二:监视 ref 定义的对象类型数据

场景三:监视 reactive 定义的对象类型数据

场景四:监视对象中的某个属性

场景五:监视多个数据

7.3 watchEffect

7.3.1 基本概念

7.3.2 watch vs watchEffect 核心区别

7.3.3 使用示例

7.4 标签的 ref 属性(模板引用)

7.4.1 基本概念

7.4.2 用在普通 DOM 标签上

7.4.3 用在组件标签上

7.5 props(父组件向子组件传值)

7.5.1 基本概念

7.5.2 三种常用声明方式

方式一:仅声明 props 名称(无校验,不推荐)

方式二:声明名称 + 类型限制(推荐基础场景)

方式三:声明名称 + 类型 + 默认值 + 必要性(推荐复杂场景)

7.6 生命周期钩子

7.6.1 基本概念

7.6.2 Vue2 vs Vue3 生命周期对比

7.6.3 常用生命周期使用示例

八、总结


一、Vue 框架初相识

1.1 Vue 是什么

        Vue(发音为 /vjuː/ ,与 “view” 相似)是一款卓越的用于构建用户界面的 JavaScript 框架 。它以标准 HTML、CSS 和 JavaScript 为基石,为开发者呈献了声明式、组件化的编程模型,极大地提升了用户界面的开发效率。Vue.js 凭借其简洁的 API 和轻量级的特性,无论是搭建简洁的静态页面交互,还是构建复杂的单页应用程序(SPA),都能轻松驾驭,成为众多前端开发者的心头好。

1.2 渐进式框架的魅力

        Vue 不仅是一个框架,更是一个生机勃勃的生态系统 。它功能全面,几乎覆盖了前端开发的常见需求场景。然而,Web 开发领域千变万化,不同开发者构建的项目在形式和规模上差异巨大。有鉴于此,Vue 在设计上着重突出灵活性以及 “可逐步集成” 的特性 。基于不同的需求场景,开发者可以多样化地运用 Vue:

  1. 渐进式增强静态 HTML(无需构建步骤):在一些简单项目或对现有静态页面进行交互升级时,无需复杂构建工具,仅通过 script 标签引入 Vue.js 文件,就能在 HTML 中运用 Vue 指令和数据绑定功能,为页面增添动态交互效果。例如,为一个静态的产品介绍页面添加点击按钮显示更多详情的功能。
  2. 作为 Web Components 嵌入任意页面:将 Vue 组件封装成 Web Components,可实现跨框架的组件复用 。即使在非 Vue 项目中,也能引入这些组件,实现功能共享。比如,在一个基于 React 框架的项目中,嵌入 Vue 编写的图表展示组件。
  3. 构建单页应用(SPA):Vue 在构建单页应用方面表现卓越,借助路由管理页面切换和状态,避免页面整体刷新,为用户带来流畅交互体验。如今许多移动端 Web 应用和后台管理系统都青睐 Vue 构建 SPA。以常见的电商移动端应用为例,用户在商品浏览、加入购物车、结算等操作过程中,页面切换流畅,无刷新延迟。
  4. 全栈 / 服务端渲染(SSR):SSR 让 Vue 应用能在服务端渲染,显著提升页面首屏加载速度和搜索引擎优化(SEO)效果 。服务器端将 Vue 组件渲染成 HTML 字符串发送到客户端,减少客户端渲染时间,对内容型网站和 SEO 要求高的项目极为有利。像一些资讯类网站,采用 SSR 后,搜索引擎爬虫能更好地抓取页面内容,提升网站搜索排名。
  5. Jamstack / 静态站点生成(SSG):SSG 是把 Vue 应用生成静态 HTML 页面,部署到 CDN 上,具备超高的性能和安全性 。适用于博客、文档网站等内容更新不频繁的项目。生成静态页面后可快速分发,减轻服务器压力。如一些技术博客网站,采用 SSG 方式,用户访问时能迅速加载页面,获取文章内容。
  6. 拓展到不同平台开发界面:借助 Electron(开发桌面端应用)、Weex(开发移动端应用)等工具和框架,Vue 能够突破传统 Web 页面限制,开发出桌面端、移动端甚至命令行终端中的多样化界面应用 。例如,通过 Electron 开发的桌面端音乐播放器应用,结合 Vue 的交互优势,为用户提供便捷的音乐播放管理界面。

1.3 单文件组件探秘

        在大多数启用构建工具的 Vue 项目里,单文件组件(*.vue 文件,即 Single - File Components,缩写为 SFC)是编写 Vue 组件的常用方式 。它以类似 HTML 的格式,将组件的模板(template)、逻辑(script)和样式(style)巧妙封装在一个文件中 。以一个简单的导航栏组件 Navbar.vue 为例:

<template>
  <nav class="navbar">
    <ul>
      <li><a href="#">首页</a></li>
      <li><a href="#">产品</a></li>
      <li><a href="#">关于我们</a></li>
    </ul>
  </nav>
</template>
<script setup lang="ts">
// 这里可以定义导航栏相关的逻辑,比如点击事件处理等
</script>
<style scoped>
.navbar {
  background-color: #333;
  color: white;
  padding: 10px 0;
}
.navbar ul {
  list-style-type: none;
  margin: 0;
  padding: 0;
  text-align: center;
}
.navbar ul li {
  display: inline;
  margin: 0 20px;
}
.navbar ul li a {
  color: white;
  text-decoration: none;
}
</style>

        这种封装方式使组件结构一目了然,维护和复用都极为方便 。每个单文件组件如同一个独立模块,拥有自己的状态、行为和样式,彼此之间互不干扰 。在 Vue 项目中,主要依靠路由切换不同的单文件组件,实现页面跳转和切换效果,仿佛整个项目在一个大的 HTML 页面中进行组件动态替换 。

二、搭建首个 Vue 项目

2.1 环境搭建先行

        创建 Vue 项目前,确保系统安装了 Node.js 。Node.js 基于 Chrome V8 引擎,让开发者能在服务器端运行 JavaScript 代码 。Vue 项目的创建和管理依赖 Node.js 的包管理工具 npm(Node Package Manager) 。在命令行输入以下命令,检查是否安装 Node.js 以及 npm 版本:

npm -v

        若系统未安装 Node.js,需前往Node.js 官方网站下载安装 。安装完成后,可能需配置环境变量,确保命令行能正确识别 npm 命令 。若已安装 Node.js,但控制台无法使用 npm 命令,同样要检查环境变量配置 。

        具体安装方法可移步此篇 亲测有效nvm + Node.js + npm 一条龙安装手册-CSDN博客

2.2 项目创建进行时

        利用最新的 Vue 项目脚手架工具 create - vue创建 Vue 项目 。在命令行进入想创建项目的目录,执行以下命令:

npm create vue@latest

        执行上述命令后,会弹出一系列交互式问题:

  1. 项目名称设定:输入期望的项目名称,如fly_vue 。
  2. TypeScript 语法选择:TypeScript 是 JavaScript 超集,为其添加了类型系统,有助于开发中发现错误,提升代码可维护性 。若选择是,项目将用 TypeScript 开发;若选择否,则用普通 JavaScript 。
  3. JSX 支持启用与否:JSX 是 JavaScript 语法扩展,常用于 React 框架,Vue 也可配置支持 。若项目需用类似 XML 语法描述 UI,可选择启用 。
  4. Vue Router 引入决策:Vue Router 是 Vue 官方路由管理器,用于单页应用页面导航和路由控制 。若项目有页面切换和路由需求,建议选择是 。
  5. Pinia 状态管理引入:Pinia 是 Vue 状态管理库,用于管理应用共享状态 。中大型项目中,多个组件间共享和管理状态时,Pinia 能提供便捷方案 。可按需选择是否引入 。
  6. Vitest 单元测试工具引入:Vitest 是基于 Vite 的单元测试框架,用于编写和运行单元测试用例,保障代码质量 。注重项目测试覆盖率和质量,可选择引入 。
  7. 端到端测试工具选择:端到端测试工具模拟用户真实环境操作,测试整个应用流程 。根据项目需求选择相应工具或选择不需要 。
  8. ESLint 代码质量检测启用:ESLint 是常用 JavaScript 代码检查工具,助开发者遵循统一代码风格规范,发现潜在问题 。建议选择是 。
  9. Prettier 代码格式化工具启用:Prettier 自动格式化代码,统一风格,提高可读性 。同样建议选择是 。

依据项目需求依次回答,create - vue工具会自动下载相关依赖,创建项目结构 。

2.3 切换至项目目录

项目创建完毕,在命令行用cd命令切换到项目目录 。若项目名称为fly_vue ,执行以下命令:

cd fly_vue

2.4 安装项目依赖包

进入项目目录后,安装项目所需依赖包 。在命令行执行:

npm install

也可用简写形式:

npm i

此命令依据项目package.json文件,下载安装项目依赖的所有 JavaScript 包,包括 Vue 核心库、插件、工具等 。

2.5 启动项目之旅

安装依赖后,在命令行执行以下命令启动项目:

npm run dev

        执行该命令,项目会在本地启动开发服务器,通常自动在浏览器打开项目页面 。若未自动打开,可在命令行查看启动信息,找到项目访问地址(如http://localhost:5173),手动在浏览器访问,就能看到项目初始页面 。

2.6 见证创建成功

        当浏览器呈现 Vue 项目初始页面,说明项目创建成功 。初始页面可能是简洁的欢迎页面,有 Vue logo 和示例内容,具体取决于创建项目时的选择和配置 。若选择默认模板,可能看到包含导航栏、内容区域和示例组件的页面,此时可开启项目定制化开发 。

三、深入剖析项目结构

了解 Vue 项目目录结构是深入学习和开发的基础 。以通过create - vue创建的典型 Vue 项目为例,其目录结构如下:

fly_vue
├── node_modules
├── public
│   ├── favicon.ico
│   ├── index.html
│   └── robots.txt
├── src
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── HelloWorld.vue
│   ├── main.js
│   ├── App.vue
│   └── router
│       └── index.js
├──.gitignore
├── index.html
├── package.json
├── README.md
└── vite.config.js
  1. node_modules:该目录存放项目依赖的所有 JavaScript 包,由npm install命令下载 。开发中切勿手动修改此目录文件 。
  2. public:包含公共静态文件,如favicon.ico(网站图标)、index.html(项目入口 HTML 文件)和robots.txt(搜索引擎爬虫规则文件) 。index.html是项目外壳,提供挂载 Vue 应用的容器 。
  3. src:项目源代码目录,Vue 组件、JavaScript 逻辑和样式皆存于此 。
    1. assets:存放静态资源,如图像、字体、样式表等 。如logo.png可能是项目图标文件 
    2. components:Vue 组件存放目录,每个组件通常是独立的.vue文件 。HelloWorld.vue是示例组件,展示 Vue 组件编写和使用方法 。
    3. main.js:项目入口 JavaScript 文件,创建 Vue 应用实例,并挂载到index.html指定 DOM 元素 。也可在此进行全局配置,如引入全局样式、注册全局组件等 。
    4. App.vue:根组件,是 Vue 应用顶层组件,其他组件多作为其子组件嵌套组合 。
    5. router:存放路由相关文件 。index.js文件定义项目路由规则,配置路由可实现页面跳转和导航 。
  4. .gitignore:指定 Git 版本控制系统无需跟踪的文件或目录,如node_modules目录通常无需提交到代码仓库 。
  5. package.json:项目配置文件,记录项目名称、版本、依赖包、脚本命令等信息 。可修改此文件管理项目依赖、运行自定义脚本 。
  6. README.md:项目说明文档,介绍项目功能、使用方法、安装步骤等,方便其他开发者了解使用项目 。
  7. vite.config.js:Vite 是现代化前端构建工具,用于开发构建 Vue 项目 。此文件是 Vite 配置文件,可配置项目开发服务器、构建选项、插件等 。

四、基础样式与内容处理

4.1 查看初始页面模样

        项目启动后,打开浏览器访问项目地址,会看到初始页面 。该页面非空,因每次创建新项目,Vue 脚手架工具会生成默认基础样式和内容 。可能是含 Vue logo 的欢迎页面,以及示例文字和组件 。这些默认内容助开发者快速熟悉项目结构和 Vue 组件用法 。

4.2 清除基础样式与内容

        若要自定义页面,常需删除默认基础样式和内容 。以App.vue根组件为例,其可能如下:

<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <header>
    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />

    <div class="wrapper">
      <HelloWorld msg="You did it!" />

      <nav>
        <RouterLink to="/">Home</RouterLink>
        <RouterLink to="/about">About</RouterLink>
      </nav>
    </div>
  </header>

  <RouterView />
</template>

<style scoped>
header {
  line-height: 1.5;
  max-height: 100vh;
}
.logo {
  display: block;
  margin: 0 auto 2rem;
}
nav {
  width: 100%;
  font-size: 12px;
  text-align: center;
  margin-top: 2rem;
}
nav a.router-link-exact-active {
  color: var(--color-text);
}
nav a.router-link-exact-active:hover {
  background-color: transparent;
}
nav a {
  display: inline-block;
  padding: 0 1rem;
  border-left: 1px solid var(--color-border);
}
nav a:first-of-type {
  border: 0;
}
@media (min-width: 1024px) {
  header {
    display: flex;
    place-items: center;
    padding-right: calc(var(--section-gap) / 2);
  }

  .logo {
    margin: 0 2rem 0 0;
  }

  header .wrapper {
    display: flex;
    place-items: flex-start;
    flex-wrap: wrap;
  }

  nav {
    text-align: left;
    margin-left: -1rem;
    font-size: 1rem;

    padding: 1rem 0;
    margin-top: 1rem;
  }
}
</style>

删除或注释模板内容,保留基本结构层代码:

<template>
  <div id="app">
    <!-- <img alt="Vue logo" src="https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fimg-home.csdnimg.cn%2Fimages%2F20230724024159.png%3Forigin_url%3D.%252Fassets%252Flogo.png%26pos_id%3DUGXr8d0z&pos_id=UGXr8d0z">
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/> -->
  </div>
</template>

同时删除样式层默认样式代码:

<style scoped>
/* #app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
} */
</style>

逻辑层代码若暂时无需引入其他组件,也可简化:

<script setup lang="ts">
// import HelloWorld from './components/HelloWorld.vue';
</script>

4.3 审视删除后的页面

        完成上述操作并保存文件 。因项目开发服务器运行中,页面会自动重新加载 。此时在浏览器查看项目页面,会发现默认页面和基础样式已清除,只剩空白页面,可着手编写自定义页面内容和样式 。

五、选项式 API 与组合式 API 辨析

5.1 基本概念解读

5.1.1 两者定义阐述

  1. 选项式 API(Options API):是 Vue2 主要编程方式,Vue3 也兼容 。它在组件对象中定义data、methods、computed、watch等选项组织代码,各选项对应组件不同功能模块 。data定义组件数据状态,methods定义组件方法,computed定义计算属性等 。这种方式代码结构清晰,易理解,适合初学者入门 。例如:
<template>
  <div>
    <p>姓名:{{ name }}</p>
    <p>年龄:{{ age }}</p>
    <button @click="increaseAge">增长年龄</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      name: '张三',
      age: 20
    };
  },
  methods: {
    increaseAge() {
      this.age++;
    }
  }
};
</script>
  1. 组合式 API(Composition API):Vue3 引入的新特性,以setup函数为入口,通过ref、reactive、computed等函数创建响应式数据和逻辑,能将相关功能代码聚合,更灵活组织组件逻辑 。用ref创建基本数据类型响应式数据,用reactive创建对象或数组的响应式数据 。组合式 API 在处理复杂组件逻辑和逻辑复用方面优势显著 。例如:
<script setup lang="ts">
import { ref } from 'vue';
const name = ref('李四');
const age = ref(22);
const increaseAge = () => {
  age.value++;
};
</script>
<template>
  <div>
    <p>姓名:{{ name }}</p>
    <p>年龄:{{ age }}</p>
    <button @click="increaseAge">增长年龄</button>
  </div>
</template>

可简单认为选项式 API(vue2)和组合式 API(vue3)是 Vue 不同写法,都能开发页面,但新技术优势更多,建议开发者重点学习组合式 API 。

5.1.2 两者区别剖析

对比维度

选项式 API

组合式 API

代码组织方式

按选项(data、methods 等)分割,相关逻辑可能分散。复杂组件中,数据定义和处理方法可能分布在不同选项,影响代码可读性和维护性。

按功能模块聚合,同一功能代码集中。将实现功能所需数据和方法组合在逻辑单元,代码结构清晰,便于理解维护。

灵活性

逻辑复用依赖 mixin,易产生命名冲突和来源不清问题。多个 mixin 可能定义相同名称属性或方法,排查问题时难确定来源。

可通过自定义函数封装逻辑,复用性高,来源清晰。开发者可将常用逻辑封装成独立函数在不同组件复用,从代码结构清晰看出逻辑来源和调用关系。

类型支持

对 TypeScript 支持较弱,类型推断有限。使用 TypeScript 时,选项式 API 中复杂类型的类型推断不准确,需开发者手动声明更多类型。

天然适配 TypeScript,类型检查完善。组合式 API 中函数和数据类型在 TypeScript 中能得到更好类型推断和检查,减少类型相关错误。

学习成本

直观易懂,适合初学者入门。基于选项的方式符合大多数开发者对面向对象编程的认知,容易理解和上手。

需理解响应式 API(如 ref、reactive),初期有一定门槛。由于涉及到一些新的概念和函数,对于新手来说,需要花费一定时间学习和理解这些响应式 API 的使用方法和原理。

适用场景

简单组件或中小型项目。对于功能简单、逻辑不复杂的组件或项目,选项式 API 能快速实现需求,开发成本较低。

复杂组件或大型项目,便于维护和扩展。在处理复杂业务逻辑和大型项目时,组合式 API 的代码组织方式更有利于团队协作和后期维护,能有效提升开发效率。

5.1.3 是否都要学习

        对于新手,可先掌握选项式 API 建立 Vue 开发基础认知,理解组件的基本概念和数据驱动思想。待熟悉后,再逐步学习组合式 API,感受其在复杂项目中的优势。对于有前端开发经验或直接学习 Vue3 的开发者,建议重点掌握组合式 API,充分发挥 Vue3 的性能优势和灵活的代码组织能力,同时了解选项式 API 以应对可能维护的 Vue2 项目。

六、Vue3 基础语法详解

6.1 模板语法

        模板语法是 Vue3 中连接逻辑与视图的核心,主要包括文本插值、原始 HTML 插入和属性绑定,覆盖了视图渲染的常见场景。

6.1.1 文本插值

        通过双大括号(Mustache 语法)实现数据与视图的绑定,支持直接渲染变量和简单 JavaScript 表达式,表达式需满足 “单一表达式” 规则(无流程控制语句,可嵌套三元运算符)。

<!-- 1. 基本变量渲染 -->
<div>{{ message }}</div>
<div>姓名: {{ name }}</div>
<!-- 2. 简单JavaScript表达式 -->
<div>{{ number + 1 }}</div> <!-- 算术运算 -->
<div>{{ ok ? 'YES' : 'NO' }}</div> <!-- 三元判断 -->
<div>{{ message.split('').reverse().join('') }}</div> <!-- 字符串方法 -->

注意:文本插值仅渲染纯文本,若内容包含 HTML 标签,会被当作普通字符串处理,需使用v-html指令渲染原始 HTML。

6.1.2 原始 HTML

使用v-html指令可将包含 HTML 标签的字符串渲染为真实 DOM 元素,需注意安全风险(避免渲染用户输入内容,防止 XSS 攻击)。

<template>
  <!-- 渲染带标签的内容 -->
  <div v-html="rawHtml"></div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
// 包含HTML标签的字符串
const rawHtml = ref('<h3 style="color: blue;">这是通过v-html渲染的标题</h3>');
</script>

6.1.3 属性绑定

使用v-bind指令(简写为:)绑定 HTML 属性与响应式数据,支持动态修改标签属性(如id、src、disabled等)。

<!-- 1. 完整语法与简写对比 -->
<div v-bind:id="dynamicId"></div> <!-- 完整语法 -->
<div :id="dynamicId"></div> <!-- 简写语法(推荐) -->
<!-- 2. 布尔值属性(如disabled):值为true时启用属性,false时移除属性 -->
<button :disabled="isButtonDisabled">禁用按钮</button>
<!-- 3. 图片src绑定(本地图片与网络图片) -->
<template>
  <!-- 方式1:直接引入本地图片 -->
  <img src="../assets/img/山水.jpg" alt="本地山水图">
  <!-- 方式2:JS中动态导入图片 -->
  <img :src="localImg" alt="动态导入本地图">
  <img :src="networkImg" alt="网络图片">
</template>
<script setup lang="ts">
import { ref } from 'vue';
// 导入本地图片(支持@别名,对应src目录)
import localImg from '@/assets/img/山水.jpg';
// 网络图片地址
const networkImg = ref('https://ts1.tc.mm.bing.net/th/id/R-C.xxxx');
</script>

使用技巧:

  1. 若数据需渲染在标签内容中(如<div>内容</div>),用文本插值{{ }};
  2. 若数据需绑定到标签属性中(如<img src="数据">),用属性绑定:。

6.2 Class 与 Style 绑定

        Vue3 支持动态绑定 CSS 类名(class)和内联样式(style),提供对象语法和数组语法两种方式,满足不同场景下的样式动态切换需求。

6.2.1 Class 绑定

        通过对象或数组语法,实现类名的条件切换和多类名组合。

<template>
  <!-- 1. 对象语法:键为类名,值为布尔值(true则添加类,false则移除) -->
  <div :class="{ active: isActive, 'text-danger': hasError }"></div>
  <!-- 2. 绑定计算属性(复杂逻辑推荐,代码更简洁) -->
  <div :class="classObject"></div>
  <!-- 3. 数组语法:直接传入类名数组(适合固定多类名) -->
  <div :class="[activeClass, errorClass]"></div>
  <!-- 4. 数组+对象语法(结合条件判断) -->
  <div :class="[{ active: isActive }, errorClass]"></div>
  <div :class="[isActive ? activeClass : '', errorClass]"></div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
// 条件变量
const isActive = ref(true);
const hasError = ref(false);
// 固定类名
const activeClass = ref('active');
const errorClass = ref('text-danger');
// 计算属性(处理复杂类名逻辑)
const classObject = computed(() => ({
  active: isActive.value,
  'text-danger': hasError.value
}));
</script>
<style>
.active { background-color: blue; }
.text-danger { color: red; }
</style>

6.2.2 Style 绑定

类似 Class 绑定,Style 绑定支持对象语法和数组语法,直接操作内联样式,属性名可使用驼峰式(fontSize)或短横线式(font-size,需加引号)。

<template>
  <!-- 1. 对象语法:键为样式属性,值为样式值 -->
  <div :style="{ color: textColor, fontSize: `${fontSize}px` }"></div>
  <!-- 2. 绑定样式对象(推荐,逻辑更清晰) -->
  <div :style="styleObject"></div>
  <!-- 3. 数组语法:应用多个样式对象(后面对象覆盖前面冲突属性) -->
  <div :style="[baseStyles, customStyles]"></div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
// 单个样式变量
const textColor = ref('red');
const fontSize = ref(16);
// 样式对象(推荐用reactive定义)
const styleObject = reactive({
  color: 'green',
  fontSize: '18px',
  fontWeight: 'bold'
});
// 多样式对象组合
const baseStyles = reactive({ padding: '10px', border: '1px solid #ccc' });
const customStyles = reactive({ backgroundColor: '#f5f5f5' });
</script>

6.3 条件渲染

        根据数据状态动态控制 DOM 元素的显示与隐藏,核心指令包括v-if/v-else-if/v-else和v-show,两者适用场景不同,需注意区分。

6.3.1 v-if / v-else-if / v-else

  1. 原理:通过 “销毁 / 重建 DOM 元素” 实现条件渲染,若条件为false,元素会从 DOM 树中移除;
  2. 特点:适合条件不频繁切换的场景(如权限控制、内容筛选结果);
  3. 注意:v-if需绑定在具体标签上,若需控制多个元素,可嵌套在<template>标签中(<template>不会被渲染为真实 DOM)。
<template>

  <!-- 1. 基础条件判断 -->

  <div v-if="type === 'A'">类型A</div>

  <div v-else-if="type === 'B'">类型B</div>

  <div v-else>其他类型</div>

  <!-- 2. 控制多个元素(用template包裹) -->

  <template v-if="showContent">

    <h1>标题</h1>

    <p>段落1</p>

    <p>段落2</p>

  </template>

</template>

<script setup lang="ts">

import { ref } from 'vue';

const type = ref('A');

const showContent = ref(true);

</script>

6.3.2 v-show

  1. 原理:通过切换 CSS 的display属性实现显示 / 隐藏(display: none或默认值),元素始终存在于 DOM 树中;
  2. 特点:适合条件频繁切换的场景(如 tab 切换、按钮显示隐藏);
  3. 注意:v-show不支持<template>标签,也不支持v-else。
<template>

  <h1 v-show="isShow">通过v-show控制显示</h1>

  <button @click="toggleShow">切换显示状态</button>

</template>

<script setup lang="ts">

import { ref } from 'vue';

const isShow = ref(true);

const toggleShow = () => {

  isShow.value = !isShow.value;

};

</script>

6.3.3 v-if vs v-show 核心区别

对比维度

v-if

v-show

DOM 存在性

条件 false 时元素被销毁(从 DOM 树移除)

元素始终存在(仅切换 display 属性)

切换性能

切换时需销毁 / 重建 DOM,性能消耗较高

仅切换 CSS,性能消耗低

初始渲染

条件 false 时不渲染元素

无论条件如何,均渲染元素(初始隐藏)

适用场景

条件不频繁切换(如权限控制)

条件频繁切换(如 tab 切换)

6.4 列表渲染

        使用v-for指令遍历数组或对象,生成重复 DOM 元素,需结合key属性优化渲染性能,避免 DOM 复用导致的逻辑错误。

6.4.1 v-for 基本用法

        支持遍历数组、对象、字符串(遍历字符)和数字(遍历从 1 到 n 的整数),语法为v-for="(item, index) in 数据源"(对象遍历为v-for="(value, key, index) in 对象")。

<template>
  <!-- 1. 遍历数组(带索引) -->
  <ul>
    <li v-for="(item, index) in fruitList" :key="item.id">
      {{ index + 1 }}. {{ item.name }} - 价格:{{ item.price }}元
    </li>
  </ul>
  <!-- 2. 遍历对象(带键名和索引) -->
  <div v-for="(value, key, index) in userInfo" :key="key">
    {{ index + 1 }}. {{ key }}: {{ value }}
  </div>
  <!-- 3. 遍历字符串(遍历每个字符) -->
  <div v-for="(char, index) in message" :key="index">
    {{ index }}: {{ char }}
  </div>
  <!-- 4. 遍历数字(从1到n) -->
  <div v-for="num in 5" :key="num">第{{ num }}个元素</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
// 数组数据源
const fruitList = ref([
  { id: 1, name: '苹果', price: 5 },
  { id: 2, name: '香蕉', price: 3 },
  { id: 3, name: '橙子', price: 4 }
]);
// 对象数据源
const userInfo = reactive({
  name: '张三',
  age: 25,
  email: 'zhangsan@example.com'
});
// 字符串数据源
const message = ref('Vue3');
</script>

6.4.2 key 属性的重要性

        key是 Vue 虚拟 DOM 算法的 “标识”,用于判断元素是否复用,需满足唯一性和稳定性(避免使用索引作为 key)。

  1. 推荐用法:使用数据源中的唯一标识(如 ID、UUID)作为 key;
  2. 不推荐用法:使用索引作为 key(若数组发生增删排序,索引会变化,导致 DOM 错误复用)。
<template>

  <!-- 推荐:用唯一ID作为key -->

  <div v-for="item in list" :key="item.id">

    {{ item.name }}

  </div>

  <!-- 不推荐:用索引作为key(数组排序/增删后易出问题) -->

  <div v-for="(item, index) in list" :key="index">

    {{ item.name }}

  </div>

</template>

6.5 事件处理

        通过v-on指令(简写为@)绑定 DOM 事件,支持方法调用、内联表达式和事件修饰符,简化事件处理逻辑。

6.5.1 基本事件监听

        支持绑定方法处理器(调用组件内方法)和内联事件处理器(直接写表达式),可通过$event参数访问原生事件对象。

<template>

  <!-- 1. 方法处理器(无参数) -->

  <button @click="handleClick">点击触发方法</button>

  <button v-on:click="handleClick">完整语法(同上)</button>

  <!-- 2. 内联事件处理器(带参数) -->

  <button @click="count++">计数+1</button>

  <button @click="sayHello('Vue')">传递参数</button>

  <!-- 3. 访问原生事件对象($event) -->

  <button @click="handleEvent($event, '参数')">传递事件对象</button>

</template>

<script setup lang="ts">

import { ref } from 'vue';

const count = ref(0);

// 无参数方法

const handleClick = () => {

  console.log('按钮被点击');

};

// 带参数方法

const sayHello = (name: string) => {

  alert(`Hello, ${name}!`);

};

// 访问原生事件对象

const handleEvent = (e: Event, msg: string) => {

  // 阻止默认行为(原生JS方式)

  e.preventDefault();

  console.log(msg, e.target);

};

</script>

6.5.2 事件修饰符

        Vue 提供内置事件修饰符,简化常见事件处理(如阻止默认行为、阻止冒泡),无需手动调用e.preventDefault()或e.stopPropagation()。

常用修饰符:

  1. .prevent:阻止默认行为(如表单提交、链接跳转);
  2. .stop:阻止事件冒泡;
  3. .once:事件仅触发一次;
  4. .capture:事件捕获模式(从外到内触发);
  5. .self:仅当事件在当前元素本身触发时才执行(排除子元素冒泡)。
<template>

  <!-- 1. 阻止表单默认提交行为 -->

  <form @submit.prevent="handleSubmit">

    <button type="submit">提交表单</button>

  </form>

  <!-- 2. 阻止事件冒泡 -->

  <div @click="parentClick">

    <button @click.stop="childClick">点击子元素(不触发父元素)</button>

  </div>

  <!-- 3. 修饰符串联 -->

  <a @click.stop.prevent="handleLink">阻止跳转+冒泡</a>

  <!-- 4. 事件仅触发一次 -->

  <button @click.once="handleOnce">仅触发一次</button>

</template>

<script setup lang="ts">

// 阻止表单提交默认行为

const handleSubmit = () => {

  console.log('表单提交逻辑');

};

// 阻止冒泡:点击子元素不触发父元素

const parentClick = () => {

  console.log('父元素事件');

};

const childClick = () => {

  console.log('子元素事件');

};

// 修饰符串联

const handleLink = () => {

  console.log('链接点击');

};

// 事件仅触发一次

const handleOnce = () => {

  console.log('仅触发一次');

};

</script>

6.5.3 按键修饰符

        监听键盘事件时(如keyup、keydown),可通过按键修饰符指定触发事件的按键(如 Enter、Ctrl)。

<template>

  <!-- 1. 按Enter键触发 -->

  <input @keyup.enter="handleEnter" placeholder="按Enter提交">

  <!-- 2. 按PageDown键触发 -->

  <input @keyup.page-down="handlePageDown" placeholder="按PageDown">

  <!-- 3. 系统修饰符(Ctrl、Alt、Shift、Meta) -->

  <input @keyup.ctrl.67="handleCopy" placeholder="Ctrl+C复制">

  <div @click.ctrl="handleCtrlClick">Ctrl+点击</div>

</template>

<script setup lang="ts">

const handleEnter = () => {

  console.log('按了Enter键');

};

const handlePageDown = () => {

  console.log('按了PageDown键');

};

const handleCopy = () => {

  console.log('Ctrl+C复制');

};

const handleCtrlClick = () => {

  console.log('Ctrl+点击');

};

</script>

6.5.4 常用事件类型汇总

事件类别

事件类型

触发时机

鼠标事件

click

鼠标单击元素时

dblclick

鼠标双击元素时

mousedown

鼠标按钮在元素上按下时

mouseup

鼠标按钮在元素上释放时

mousemove

鼠标在元素上移动时

mouseover

鼠标移入元素时

mouseout

鼠标移出元素时

键盘事件

keydown

按下任意键时(包括功能键)

keyup

释放按键时

keypress

按下字符键(可打印字符)时(已逐步废弃,推荐用 keydown)

表单事件

submit

表单提交时(如点击提交按钮)

input

表单输入框内容实时变化时

change

表单元素值改变且失去焦点时(如下拉框选择、输入框失焦)

focus

表单元素获得焦点时(如点击输入框)

blur

表单元素失去焦点时(如点击输入框外)

窗口事件

load

页面或图片加载完成后

unload

页面关闭或刷新时

resize

浏览器窗口大小改变时

scroll

页面滚动时

触摸事件(移动端)

touchstart

手指触摸屏幕时

touchmove

手指在屏幕上滑动时

touchend

手指离开屏幕时

6.6 表单输入绑定

        使用v-model指令实现表单元素与响应式数据的 “双向绑定”,即数据变化时视图自动更新,视图输入时数据自动同步,支持文本、复选框、单选按钮、下拉框等常见表单元素。

6.6.1 基础用法

<template>

  <!-- 1. 文本输入框(单行) -->

  <input v-model="message" placeholder="输入文本">

  <p>输入的文本:{{ message }}</p>

  <!-- 2. 多行文本框 -->

  <textarea v-model="content" placeholder="输入多行文本"></textarea>

  <p>多行文本:{{ content }}</p>

  <!-- 3. 复选框(单个:绑定布尔值) -->

  <input type="checkbox" id="agree" v-model="isAgree">

  <label for="agree">同意协议</label>

  <p>是否同意:{{ isAgree }}</p>

  <!-- 4. 复选框(多个:绑定数组,value为数组元素) -->

  <div>

    <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">

    <label for="jack">Jack</label>

    <input type="checkbox" id="john" value="John" v-model="checkedNames">

    <label for="john">John</label>

    <p>选中的名字:{{ checkedNames }}</p>

  </div>

  <!-- 5. 单选按钮(绑定单个值) -->

  <div>

    <input type="radio" id="one" value="One" v-model="picked">

    <label for="one">选项一</label>

    <input type="radio" id="two" value="Two" v-model="picked">

    <label for="two">选项二</label>

    <p>选中的选项:{{ picked }}</p>

  </div>

  <!-- 6. 下拉框(单选:绑定选中的value) -->

  <select v-model="selected">

    <option disabled value="">请选择</option> <!-- 初始空值,需设置disabled -->

    <option>A</option>

    <option>B</option>

    <option>C</option>

  </select>

  <p>选中的下拉项:{{ selected }}</p>

  <!-- 7. 下拉框(多选:绑定数组,需加multiple属性) -->

  <select v-model="selectedMulti" multiple>

    <option>A</option>

    <option>B</option>

    <option>C</option>

  </select>

  <p>选中的多个下拉项:{{ selectedMulti }}</p>

</template>

<script setup lang="ts">

import { ref } from 'vue';

// 1. 文本输入

const message = ref('');

// 2. 多行文本

const content = ref('');

// 3. 单个复选框

const isAgree = ref(false);

// 4. 多个复选框

const checkedNames = ref<string[]>([]);

// 5. 单选按钮

const picked = ref('');

// 6. 下拉框(单选)

const selected = ref('');

// 7. 下拉框(多选)

const selectedMulti = ref<string[]>([]);

</script>

6.6.2 v-model 修饰符

v-model提供三个常用修饰符,优化表单输入处理:

  1. .lazy:从 “input 事件同步” 改为 “change 事件同步”(失去焦点或按 Enter 时同步数据);
  2. .number:自动将输入值转为数字类型(适用于数字输入框,避免获取到字符串);
  3. .trim:自动过滤输入值的首尾空白字符。
<template>

  <!-- 1. .lazy:失去焦点同步数据 -->

  <input v-model.lazy="lazyMsg" placeholder="输入后失焦同步">

  <p>lazy同步:{{ lazyMsg }}</p>

  <!-- 2. .number:转为数字类型 -->

  <input v-model.number="age" type="number" placeholder="输入数字">

  <p>年龄(数字类型):{{ age }},类型:{{ typeof age }}</p>

  <!-- 3. .trim:过滤首尾空白 -->

  <input v-model.trim="trimMsg" placeholder="输入带空格的文本">

  <p>trim后:{{ trimMsg }},长度:{{ trimMsg.length }}</p>

</template>

<script setup lang="ts">

import { ref } from 'vue';

const lazyMsg = ref('');

const age = ref(0); // 初始为数字类型

const trimMsg = ref('');

</script>

6.6.3 组合式 API 完整示例

整合模板语法、响应式数据和事件处理,实现一个完整的表单交互示例:

<template>

  <div class="form-demo">

    <!-- 文本插值 -->

    <h1>{{ title }}</h1>

    <!-- Class绑定 -->

    <div :class="{ active: isActive, error: hasError }" class="form-group">

      <label>用户名:</label>

      <input v-model="username" @blur="checkUsername" placeholder="输入用户名">

      <span v-if="usernameError" class="error-text">{{ usernameError }}</span>

    </div>

    <!-- 条件渲染 -->

    <p v-if="showTips">用户名长度需大于3位</p>

    <!-- 列表渲染 -->

    <div class="hobby-group">

      <label>爱好:</label>

      <div v-for="(hobby, index) in hobbies" :key="index">

        <input type="checkbox" :id="`hobby-${index}`" :value="hobby" v-model="selectedHobbies">

        <label :for="`hobby-${index}`">{{ hobby }}</label>

      </div>

    </div>

    <!-- 事件处理 -->

    <button @click="submitForm" :disabled="!isFormValid">提交</button>

    <!-- 表单绑定结果 -->

    <div v-if="submitted" class="result">

      <h3>提交结果:</h3>

      <p>用户名:{{ username }}</p>

      <p>爱好:{{ selectedHobbies.join(', ') }}</p>

    </div>

  </div>

</template>

<script setup lang="ts">

import { ref, computed } from 'vue';

// 响应式数据

const title = ref('Vue3 表单示例');

const isActive = ref(false);

const hasError = ref(false);

const username = ref('');

const usernameError = ref('');

const showTips = ref(true);

const hobbies = ref(['篮球', '游戏', '阅读']);

const selectedHobbies = ref<string[]>([]);

const submitted = ref(false);

// 计算属性:判断表单是否有效

const isFormValid = computed(() => {

  return username.value.length > 3 && selectedHobbies.value.length > 0;

});

// 方法:检查用户名

const checkUsername = () => {

  if (username.value.length <= 3) {

    usernameError.value = '用户名长度需大于3位';

    hasError.value = true;

    isActive.value = false;

  } else {

    usernameError.value = '';

    hasError.value = false;

    isActive.value = true;

  }

};

// 方法:提交表单

const submitForm = () => {

  checkUsername();

  if (isFormValid.value) {

    submitted.value = true;

    // 模拟提交逻辑(如接口请求)

    console.log('表单提交:', { username: username.value, hobbies: selectedHobbies.value });

  }

};

</script>

<style scoped>

.form-demo { max-width: 600px; margin: 20px auto; padding: 20px; }

.form-group { margin-bottom: 15px; padding: 10px; border: 1px solid #ccc; }

.active { border-color: #42b983; }

.error { border-color: #ff4444; }

.error-text { color: #ff4444; font-size: 12px; margin-left: 10px; }

.hobby-group { margin-bottom: 15px; }

.hobby-group div { display: inline-block; margin-right: 15px; }

button { padding: 8px 16px; background-color: #42b983; color: white; border: none; border-radius: 4px; cursor: pointer; }

button:disabled { background-color: #ccc; cursor: not-allowed; }

.result { margin-top: 20px; padding: 10px; border: 1px solid #42b983; }

</style>

七、Vue3 核心语法进阶

7.1 computed 计算属性

7.1.1 基本概念

        computed 计算属性的核心作用是根据已有响应式数据,动态计算出新数据,且具备缓存机制—— 仅当依赖的响应式数据变化时,才会重新计算,避免重复执行复杂逻辑,提升性能。

7.1.2 只读计算属性

        适用于 “仅根据依赖数据计算结果,无需手动修改计算结果” 的场景,语法为computed(() => { 计算逻辑 })。

<template>

  <div class="person">

    <h3>只读计算属性示例</h3>

    <div>

      姓:<input type="text" v-model="firstName" placeholder="输入姓氏">

    </div>

    <div>

      名:<input type="text" v-model="lastName" placeholder="输入名字">

    </div>

    <div>

      全名:<span>{{ fullName }}</span> <!-- 自动同步计算结果 -->

    </div>

  </div>

</template>

<script setup lang="ts" name="ReadOnlyComputed">

import { ref, computed } from 'vue';

// 依赖的响应式数据

const firstName = ref('张');

const lastName = ref('三');

// 只读计算属性:依赖firstName和lastName,动态计算全名

const fullName = computed(() => {

  // 仅当firstName或lastName变化时,此函数才会重新执行

  return `${firstName.value}-${lastName.value}`;

});

</script>

7.1.3 可读写计算属性

        适用于 “需手动修改计算结果,并反向更新依赖数据” 的场景,语法为computed({ get() {}, set(val) {} }),其中:

  1. get():读取计算结果时执行(同只读计算属性逻辑);
  2. set(val):修改计算结果时执行(接收修改后的值,反向更新依赖数据)。
<template>

  <div class="person">

    <h3>可读写计算属性示例</h3>

    <div>

      姓:<input type="text" v-model="firstName" placeholder="输入姓氏">

    </div>

    <div>

      名:<input type="text" v-model="lastName" placeholder="输入名字">

    </div>

    <div>

      全名:<span>{{ fullName }}</span>

    </div>

    <button @click="changeFullName">点击修改全名为“李-四”</button>

  </div>

</template>

<script setup lang="ts" name="ReadWriteComputed">

import { ref, computed } from 'vue';

const firstName = ref('张');

const lastName = ref('三');

// 可读写计算属性

const fullName = computed({

  // 读取时执行:计算全名

  get() {

    return `${firstName.value}-${lastName.value}`;

  },

  // 修改时执行:拆分传入的新值,反向更新firstName和lastName

  set(val: string) {

    console.log('手动修改了fullName,新值为:', val);

    // 假设新值格式为“姓-名”,拆分后更新依赖数据

    const [newFirst, newLast] = val.split('-');

    firstName.value = newFirst || '';

    lastName.value = newLast || '';

  }

});

// 手动修改计算属性

const changeFullName = () => {

  fullName.value = '李-四'; // 触发set()方法

};

</script>

7.2 watch 监听器

7.2.1 基本概念

watch 监听器的核心作用是监视响应式数据的变化,当数据变化时执行自定义逻辑(如打印日志、发起请求、更新其他数据)。Vue3 中 watch 仅支持监视以下四种数据:

  1. ref 定义的基本类型数据;
  2. ref 定义的对象类型数据;
  3. reactive 定义的对象类型数据;
  4. 函数返回值(getter 函数,如() => obj.xxx);
  5. 包含上述数据的数组(同时监视多个数据)。

7.2.2 五种常见使用场景

场景一:监视 ref 定义的基本类型数据

直接传入 ref 数据名,监视其value的变化,回调函数接收newValue(新值)和oldValue(旧值)。

<template>

  <div class="person">

    <h3>场景一:监视ref基本类型数据</h3>

    <p>当前求和:{{ sum }}</p>

    <button @click="sum += 1">点击sum+1</button>

  </div>

</template>

<script setup lang="ts" name="WatchRefBasic">

import { ref, watch } from 'vue';

const sum = ref(0);

// 监视ref基本类型数据:sum的value变化时触发

const stopWatch = watch(sum, (newValue, oldValue) => {

  console.log('sum变化了:', '新值=', newValue, '旧值=', oldValue);

  // 可选:满足条件时停止监视(调用watch返回的stop函数)

  if (newValue >= 10) {

    stopWatch();

    console.log('sum达到10,停止监视');

  }

});

</script>
场景二:监视 ref 定义的对象类型数据
  1. 直接传入 ref 数据名:默认监视对象的地址值(仅当对象被整体替换时触发);
  2. 需监视对象内部属性:需手动开启deep: true(深度监视),此时newValue和oldValue均为新对象(因指向同一地址)。
<template>

  <div class="person">

    <h3>场景二:监视ref对象类型数据</h3>

    <p>姓名:{{ person.name }}</p>

    <p>年龄:{{ person.age }}</p>

    <button @click="person.name += '~'">修改姓名</button>

    <button @click="person.age += 1">修改年龄</button>

    <button @click="person = { name: '李四', age: 30 }">整体替换person</button>

  </div>

</template>

<script setup lang="ts" name="WatchRefObject">

import { ref, watch } from 'vue';

// ref定义的对象类型数据

const person = ref({

  name: '张三',

  age: 18

});

// 监视ref对象:需开启deep才能监视内部属性变化

watch(

  person,

  (newValue, oldValue) => {

    console.log('person变化了:', '新值=', newValue, '旧值=', oldValue);

    // 注意:修改内部属性时,newValue和oldValue指向同一对象(地址相同)

  },

  { deep: true } // 开启深度监视

);

</script>
场景三:监视 reactive 定义的对象类型数据

reactive 定义的对象默认开启深度监视,无需手动设置deep: true,且修改对象内部任意层级属性都会触发监视。

<template>

  <div class="person">

    <h3>场景三:监视reactive对象类型数据</h3>

    <p>姓名:{{ user.name }}</p>

    <p>年龄:{{ user.age }}</p>

    <p>地址:{{ user.address.city }}</p>

    <button @click="user.name += '~'">修改姓名</button>

    <button @click="user.address.city = '上海'">修改城市</button>

  </div>

</template>

<script setup lang="ts" name="WatchReactiveObject">

import { reactive, watch } from 'vue';

// reactive定义的对象类型数据

const user = reactive({

  name: '张三',

  age: 18,

  address: {

    city: '北京'

  }

});

// 监视reactive对象:默认深度监视,内部任意属性变化都触发

watch(user, (newValue, oldValue) => {

  console.log('user变化了:', '新值=', newValue, '旧值=', oldValue);

});

</script>
场景四:监视对象中的某个属性

若需精确监视对象中的单个属性(而非整个对象),需通过getter 函数指定属性(() => obj.xxx),避免不必要的触发。

<template>

  <div class="person">

    <h3>场景四:监视对象中的某个属性</h3>

    <p>姓名:{{ user.name }}</p>

    <p>年龄:{{ user.age }}</p>

    <button @click="user.name += '~'">修改姓名</button>

    <button @click="user.age += 1">修改年龄</button>

  </div>

</template>

<script setup lang="ts" name="WatchObjectProperty">

import { reactive, watch } from 'vue';

const user = reactive({

  name: '张三',

  age: 18

});

// 监视user.name(基本类型属性):用getter函数指定

watch(

  () => user.name,

  (newName, oldName) => {

    console.log('user.name变化了:', '新值=', newName, '旧值=', oldName);

  }

);

// 若属性是对象类型(如user.address),需开启deep

const userWithAddr = reactive({

  address: { city: '北京' }

});

watch(

  () => userWithAddr.address, // 监视对象类型属性

  (newAddr) => {

    console.log('address变化了:', newAddr);

  },

  { deep: true } // 需深度监视内部属性(如city)

);

</script>
场景五:监视多个数据

通过数组传入多个需监视的数据(支持 ref、reactive、getter 函数),回调函数的newValue和oldValue也为数组,顺序与传入数据一致。

<template>

  <div class="person">

    <h3>场景五:监视多个数据</h3>

    <p>姓名:{{ user.name }}</p>

    <p>年龄:{{ age }}</p>

    <button @click="user.name += '~'">修改姓名</button>

    <button @click="age += 1">修改年龄</button>

  </div>

</template>

<script setup lang="ts" name="WatchMultipleData">

import { reactive, ref, watch } from 'vue';

const user = reactive({ name: '张三' });

const age = ref(18);

// 监视多个数据:数组形式传入

watch(

  [() => user.name, age], // 监视user.name和age

  (newValues, oldValues) => {

    // newValues[0]对应user.name的新值,newValues[1]对应age的新值

    console.log('多个数据变化:', '新值=', newValues, '旧值=', oldValues);

  }

);

</script>

7.3 watchEffect

7.3.1 基本概念

        watchEffect 是 Vue3 新增的 “副作用监听器”,核心特点是自动追踪依赖—— 无需明确指定监视的数据,函数内部用到的响应式数据变化时,会自动重新执行函数。

7.3.2 watch vs watchEffect 核心区别

对比维度

watch

watchEffect

依赖指定

需明确指出监视的数据(如watch(sum, () => {}))

无需指定,自动追踪函数内用到的响应式数据

执行时机

默认 “数据变化后执行”,可通过immediate: true设置 “初始执行”

默认 “初始执行一次”,之后依赖变化时重新执行

使用场景

需明确知道 “哪个数据变化”、需获取oldValue、需控制执行时机

只需 “依赖变化时执行副作用”(如请求、DOM 操作),无需关注具体数据

7.3.3 使用示例

以 “水温或水位达标时联系服务器” 为例,对比 watch 和 watchEffect 的实现差异:

<template>

  <div class="person">

    <h3>watchEffect示例:水温/水位监控</h3>

    <p>水温:{{ temp }}℃</p>

    <p>水位:{{ height }}cm</p>

    <button @click="temp += 10">水温+10</button>

    <button @click="height += 5">水位+5</button>

  </div>

</template>

<script setup lang="ts" name="WatchEffectDemo">

import { ref, watch, watchEffect } from 'vue';

const temp = ref(0);

const height = ref(0);

// 1. 用watch实现:需明确指定监视temp和height

watch(

  [temp, height],

  ([newTemp, newHeight]) => {

    if (newTemp >= 50 || newHeight >= 20) {

      console.log('watch:联系服务器(水温或水位达标)');

    }

  }

);

// 2. 用watchEffect实现:自动追踪temp和height

const stopEffect = watchEffect(() => {

  // 函数内用到temp和height,自动作为依赖

  if (temp.value >= 50 || height.value >= 20) {

    console.log('watchEffect:联系服务器(水温或水位达标)');

  }

  // 满足条件时停止监听

  if (temp.value >= 100 || height.value >= 50) {

    stopEffect();

    console.log('watchEffect:停止监控');

  }

});

</script>

7.4 标签的 ref 属性(模板引用)

7.4.1 基本概念

        模板引用(ref属性)的核心作用是获取 DOM 元素或组件实例,需配合ref()函数创建响应式变量,变量名需与模板中ref属性值一致。

7.4.2 用在普通 DOM 标签上

        获取指定 DOM 元素,可直接操作 DOM(如修改样式、获取内容),需注意DOM 元素在组件挂载后才能获取(避免在setup初始阶段直接访问)。

<template>

  <div class="person">

    <h3>模板引用:获取普通DOM</h3>

    <h1 ref="titleRef">Vue3 模板引用</h1>

    <input ref="inputRef" type="text" placeholder="输入内容">

    <button @click="logDOM">点击打印DOM信息</button>

  </div>

</template>

<script setup lang="ts" name="RefDom">

import { ref, onMounted } from 'vue';

// 创建与模板ref属性同名的ref变量(初始值为undefined)

const titleRef = ref<HTMLElement | null>(null);

const inputRef = ref<HTMLInputElement | null>(null);

// 点击事件:操作DOM

const logDOM = () => {

  // 访问DOM需通过.value,且需判断非null

  if (titleRef.value) {

    console.log('标题文本:', titleRef.value.innerText);

    titleRef.value.style.color = 'blue'; // 修改DOM样式

  }

  if (inputRef.value) {

    console.log('输入框内容:', inputRef.value.value);

    inputRef.value.focus(); // 让输入框获焦

  }

};

// 组件挂载后,DOM已存在,可安全访问

onMounted(() => {

  if (inputRef.value) {

    inputRef.value.placeholder = '挂载后修改占位符';

  }

});

</script>

7.4.3 用在组件标签上

        获取子组件实例,可调用子组件的方法或访问子组件的响应式数据,但需子组件通过defineExpose主动暴露需外部访问的内容(默认不暴露)。

<!-- 父组件:Parent.vue -->

<template>

  <div class="parent">

    <h3>父组件:获取子组件实例</h3>

    <!-- 子组件标签添加ref属性 -->

    <Child ref="childRef" />

    <button @click="accessChild">点击访问子组件</button>

  </div>

</template>

<script setup lang="ts" name="Parent">

import { ref } from 'vue';

import Child from './Child.vue';

// 创建ref变量,指向子组件实例

const childRef = ref<InstanceType<typeof Child> | null>(null);

// 访问子组件的方法和数据

const accessChild = () => {

  if (childRef.value) {

    // 调用子组件暴露的方法

    childRef.value.sayHello();

    // 访问子组件暴露的响应式数据

    console.log('子组件名称:', childRef.value.childName);

    // 修改子组件暴露的响应式数据

    childRef.value.childName = '修改后的子组件名称';

  }

};

</script>

<!-- 子组件:Child.vue -->

<template>

  <div class="child">

    <h4>子组件</h4>

    <p>子组件名称:{{ childName }}</p>

  </div>

</template>

<script setup lang="ts" name="Child">

import { ref, defineExpose } from 'vue';

// 子组件的响应式数据

const childName = ref('初始子组件名称');

// 子组件的方法

const sayHello = () => {

  alert('Hello,我是子组件!');

};

// 主动暴露需外部访问的内容(不暴露则父组件无法访问)

defineExpose({

  childName, // 暴露响应式数据

  sayHello   // 暴露方法

});

</script>

7.5 props(父组件向子组件传值)

7.5.1 基本概念

        props 是 Vue 中 “父组件向子组件传递数据” 的核心方式,子组件需通过defineProps声明接收的 props,支持类型限制、默认值、必要性校验,确保数据传递的规范性。

7.5.2 三种常用声明方式

方式一:仅声明 props 名称(无校验,不推荐)
<!-- 子组件:Child.vue -->

<script setup lang="ts" name="Child">

// 仅声明接收的props名称,无类型和校验

const props = defineProps(['list', 'title']);

</script>
方式二:声明名称 + 类型限制(推荐基础场景)
<!-- 子组件:Child.vue -->

<script setup lang="ts" name="Child">

// 导入自定义类型(可选,用于复杂类型)

import type { Person } from '@/types';

// 声明props并限制类型

const props = defineProps<{

  list: Person[]; // 数组类型(基于自定义Person接口)

  title: string;  // 字符串类型

  count?: number; // 可选类型(加?)

}>();

</script>
方式三:声明名称 + 类型 + 默认值 + 必要性(推荐复杂场景)

通过withDefaults为 props 设置默认值,同时支持必要性校验(非可选属性必须传值)。

<!-- 子组件:Child.vue -->

<template>

  <div class="child">

    <h3>{{ title }}</h3>

    <ul>

      <li v-for="(item, index) in list" :key="index">

        {{ item.name }} - {{ item.age }}岁

      </li>

    </ul>

    <p>计数:{{ count }}</p>

  </div>

</template>

<script setup lang="ts" name="Child">

// 1. 定义自定义类型(可选,放在单独types文件中更规范)

interface Person {

  name: string;

  age: number;

}

// 2. 声明props:类型+默认值+必要性

const props = withDefaults(

  defineProps<{

    // 必传属性(无?):父组件必须传递

    list: Person[];

    title: string;

    // 可选属性(有?):父组件可不传,使用默认值

    count?: number;

  }>(),

  {

    // 为可选属性设置默认值(函数形式用于引用类型,避免共享引用)

    count: 0,

    // 数组/对象类型默认值需用函数返回(避免多个组件共享同一数组)

    list: () => [{ name: '默认用户', age: 18 }]

  }

);

// 访问props(无需.value,直接使用)

console.log('父组件传递的标题:', props.title);

</script>

<!-- 父组件:Parent.vue -->

<template>

  <div class="parent">

    <h3>父组件传值给子组件</h3>

    <!-- 传递props:必传属性list和title,可选属性count -->

    <Child

      :list="personList"

      title="用户列表"

      :count="5"

    />

  </div>

</template>

<script setup lang="ts" name="Parent">

import { ref } from 'vue';

import Child from './Child.vue';

// 父组件的数据源

const personList = ref([

  { name: '张三', age: 18 },

  { name: '李四', age: 20 }

]);

</script>

7.6 生命周期钩子

7.6.1 基本概念

        生命周期钩子是 Vue 组件在 “创建、挂载、更新、卸载” 等阶段自动执行的函数,开发者可在特定阶段插入自定义逻辑(如初始化数据、发起请求、清理资源)。

7.6.2 Vue2 vs Vue3 生命周期对比

        Vue3 在 Composition API 中调整了部分生命周期钩子的名称,移除了beforeCreate和created(由setup函数替代),核心对应关系如下:

Vue2 生命周期(Options API)

Vue3 生命周期(Composition API)

触发阶段

beforeCreate

setup函数(替代)

组件实例创建前

created

setup函数(替代)

组件实例创建后

beforeMount

onBeforeMount

DOM 挂载前(模板已编译,未插入页面)

mounted

onMounted

DOM 挂载后(组件已渲染到页面,可操作 DOM)

beforeUpdate

onBeforeUpdate

响应式数据变化后,DOM 更新前

updated

onUpdated

DOM 更新后(避免在此时修改数据,可能导致无限循环)

beforeDestroy

onBeforeUnmount

组件卸载前(可清理定时器、事件监听等)

destroyed

onUnmounted

组件卸载后(完全从 DOM 树移除,资源已释放)

7.6.3 常用生命周期使用示例

<template>
  <div class="person">
    <h3>Vue3 生命周期示例</h3>
    <p>当前计数:{{ count }}</p>
    <button @click="count += 1">点击+1</button>
  </div>
</template>

<script setup lang="ts" name="LifecycleDemo">
import {
  ref,
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted
} from 'vue';

const count = ref(0);
let timer: number | null = null;

// 1. setup函数:替代 beforeCreate 和 created,组件初始化时执行(<script setup> 语法下代码即位于此阶段)
console.log('setup:组件初始化(替代 beforeCreate 和 created)');
// 此时组件实例尚未创建完成,访问不到 this;可执行:
// · 响应式数据、计算属性、侦听器定义
// · 事件总线/全局状态订阅
// · 纯函数工具导入等

// 2. onBeforeMount:DOM 挂载前(模板已编译,但尚未插入页面)
onBeforeMount(() => {
  console.log('onBeforeMount:DOM 挂载前,根元素尚不存在于 document');
  // 可用来在真实 DOM 生成前读取【编译后】的 vnode 信息
  // 或做最后一次数据拦截、权限校验
});

// 3. onMounted:DOM 挂载后(组件已渲染到页面,可安全操作 DOM / 发起异步请求)
onMounted(() => {
  console.log('onMounted:DOM 挂载完成,可获取 ref、调用第三方库');
  // 典型用途:
  // · 获取图表、地图等需要真实节点的第三方实例
  // · 启动定时器、WebSocket 连接
  timer = window.setInterval(() => {
    console.log('心跳包');
  }, 2000);
});

// 4. onBeforeUpdate:响应式数据变化后,DOM 更新前(可读取旧 DOM)
onBeforeUpdate(() => {
  console.log('onBeforeUpdate:数据已变,DOM 即将打补丁');
  // 适合做更新前“快照”:滚动位置、元素尺寸、动画中断重置
});

// 5. onUpdated:DOM 更新后(避免在此直接修改响应式数据,可能引发无限循环)
onUpdated(() => {
  console.log('onUpdated:DOM 已完成补丁,页面与数据同步');
  // 可执行与更新后相关的副作用,如:
  // · 重新计算滚动条高度
  // · 通知父组件“渲染已稳定”
  // 注意:若必须改数据,请使用 nextTick 或条件判断,防止死循环
});

// 6. onBeforeUnmount:组件卸载前(清理定时器、事件监听、WebSocket 等)
onBeforeUnmount(() => {
  console.log('onBeforeUnmount:组件即将卸载,适合释放资源');
  if (timer) {
    clearInterval(timer);
    timer = null;
  }
  // 也可解除全局事件总线、取消未完成的 axios 请求
});

// 7. onUnmounted:组件卸载后(已从 DOM 树移除,实例即将销毁)
onUnmounted(() => {
  console.log('onUnmounted:组件完全卸载,所有指令/子组件已销毁');
  // 可执行最后的兜底清理、埋点上报、日志记录
});
</script>

八、总结

        通过本文的梳理,我们从 Vue 框架的基础认知出发,逐步深入到项目实战与核心 API 对比,完整覆盖了 Vue 入门阶段的关键知识点。首先,我们明确了 Vue 作为渐进式 JavaScript 框架的定位 —— 它既可以轻量地增强静态 HTML,也能支撑复杂的单页应用、服务端渲染等场景,而单文件组件的设计则让组件的封装与复用变得清晰高效。        

        在项目创建环节,我们详细拆解了从环境准备(Node.js/npm 安装)到使用create-vue脚手架创建项目、安装依赖、启动服务的全流程,并针对交互式配置选项给出了选择建议,帮助初学者避开环境配置与项目初始化的常见坑点。同时,通过项目结构解析,让大家理解了src目录下组件、资源、入口文件的分工,以及public、node_modules等目录的作用,为后续开发奠定了目录认知基础。

        在基础开发环节,我们通过清除默认样式与内容的实操,展示了 Vue 项目自定义开发的起点;而对选项式 API 与组合式 API 的深度对比,则帮助大家理解两种编程范式的差异 —— 选项式 API 适合简单场景与初学者入门,组合式 API 则在复杂项目的逻辑聚合、TypeScript 支持、逻辑复用方面更具优势,同时给出了 “新手先学选项式 API 打基础,再进阶组合式 API” 的学习路径建议。

        对于刚接触 Vue 的开发者而言,本文的内容可作为入门指南:先通过项目创建实操熟悉开发流程,再通过简单组件开发理解单文件组件的结构,最后结合实际需求选择合适的 API 范式进行开发。随着对 Vue 理解的加深,可进一步探索 Vue Router 路由管理、Pinia 状态管理等进阶内容,逐步构建起完整的 Vue 技术栈能力。

        Vue 的核心优势在于其简洁的 API 设计与灵活的适用场景,无论是小型项目的快速迭代,还是大型项目的模块化开发,都能提供高效的解决方案。希望本文能帮助大家顺利开启 Vue 学习之旅,在实际开发中不断积累经验,充分发挥 Vue 框架的优势。

        若在实践中遇到问题,可在评论区留言,Fly帮你在线答疑!!!

更多推荐