ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

vue3快速入门

2026/9/17 21:15:06 拓冰建站 浏览量
vue3快速入门

文章目录

  • 1. Vue3简介
    • 1.1. 性能的提升
    • 1.2.源码的升级
    • 1.3. 拥抱TypeScript
    • 1.4. 新的特性
  • 2. 创建Vue3工程
    • 2.1. 基于 vue-cli 创建
    • 2.2. 基于 vite 创建(推荐)
      • vite介绍
      • 创建步骤
      • 项目结构
        • 安装插件
        • 项目结构
        • 总结
    • 2.3. 一个简单的效果
      • Person.vue
      • App.vue
  • 3. Vue3核心语法
    • 3.1. OptionsAPI 与 CompositionAPI
      • Options API 的弊端
      • Composition API 的优势
    • 3.2. 拉开序幕的 setup
      • setup 概述
      • setup 的返回值
      • setup 与 Options API 的关系
      • setup 语法糖
    • 3.3. ref 创建:基本类型的响应式数据
    • 3.4. reactive 创建:对象类型的响应式数据
    • 3.5. ref 创建:对象类型的响应式数据
    • 3.6. ref 对比 reactive
      • 宏观角度
      • 区别
      • 使用原则
    • 3.7 toRefs 与 toRef
      • 现象
      • toRefs&toRef的使用
    • 3.8 computed
    • 3.9 watch
      • 作用
      • 特点
      • 场景
        • * 情况一
        • * 情况二
          • 示例1
          • 示例2
        • * 情况三
        • * 情况四
          • 没有监视的代码
          • 监视reactive定义的对象类型中的某个基本属性
          • 监视reactive定义的对象类型中的某个对象属性
        • * 情况五

1. Vue3简介

  • 2020年9月18日,Vue.js发布版3.0版本,代号:One Piece(n

  • 经历了:4800+次提交、40+个RFC、600+次PR、300+贡献者

  • 官方发版地址:Release v3.0.0 One Piece · vuejs/core

  • 截止2023年10月,最新的公开版本为:3.3.4

    在这里插入图片描述

1.1. 性能的提升

  • 打包大小减少41%

  • 初次渲染快55%, 更新渲染快133%

  • 内存减少54%

1.2.源码的升级

  • 使用Proxy代替defineProperty实现响应式。

  • 重写虚拟DOM的实现和Tree-Shaking

1.3. 拥抱TypeScript

  • Vue3可以更好的支持TypeScript

1.4. 新的特性

  1. Composition API(组合API):

    • setup

    • refreactive

    • computedwatch

  2. 新的内置组件:

    • Fragment

    • Teleport

    • Suspense

  3. 其他改变:

    • 新的生命周期钩子

    • data 选项应始终被声明为一个函数

    • 移除keyCode支持作为 v-on 的修饰符

2. 创建Vue3工程

2.1. 基于 vue-cli 创建

点击查看 Vue-Cli 官方文档,(基于vue-cli创建,其实就是基于webpack来创建vue项目)

备注:目前vue-cli已处于维护模式,官方推荐基于 Vite 创建项目。

## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version## 安装或者升级你的@vue/cli 
npm install -g @vue/cli## 执行创建命令
vue create vue_test##  随后选择3.x
##  Choose a version of Vue.js that you want to start the project with (Use arrow keys)
##  > 3.x
##    2.x## 启动
cd vue_test
npm run serve

2.2. 基于 vite 创建(推荐)

vite介绍

vite 是新一代前端构建工具,官网地址:https://vitejs.cn,vite的优势如下:

  • 轻量快速的热重载(HMR),能实现极速的服务启动。
  • TypeScriptJSXCSS 等支持开箱即用(不用配置,直接就可以用)。
  • 真正的按需编译,不再等待整个应用编译完成。
  • webpack构建 与 vite构建对比图如下:
    在这里插入图片描述
    在这里插入图片描述

创建步骤

具体操作如下(点击查看官方文档)

## 1.创建命令(基于vite创建vue3项目,前提是需要安装nodejs环境)
npm create vue@latest## 2.具体配置
## 配置项目名称
√ Project name: vue3_test
## 是否添加TypeScript支持
√ Add TypeScript?  Yes
## 是否添加JSX支持
√ Add JSX Support?  No
## 是否添加路由环境
√ Add Vue Router for Single Page Application development?  No
## 是否添加pinia环境
√ Add Pinia for state management?  No
## 是否添加单元测试
√ Add Vitest for Unit Testing?  No
## 是否添加端到端测试方案
√ Add an End-to-End Testing Solution? » No
## 是否添加ESLint语法检查
√ Add ESLint for code quality?  Yes
## 是否添加Prettiert代码格式化
√ Add Prettier for code formatting?  No

构建过程如下:

在这里插入图片描述

访问vue3项目如下:

在这里插入图片描述

项目结构

安装插件

安装官方推荐的vscode插件:

在这里插入图片描述
在这里插入图片描述

项目结构

在这里插入图片描述

index.html

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><link rel="icon" href="/favicon.ico"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vite App</title></head><body><div id="app"></div><script type="module" src="/src/main.ts"></script></body>
</html>

main.ts

import './assets/main.css'// 引入createApp用于创建应用
import { createApp } from 'vue'// 引入App根组件
import App from './App.vue'createApp(App).mount('#app')

App.vue

<!-- 自己动手编写的一个App组件 -->
<template><div class="app"><h1>你好啊!</h1></div>
</template><script lang="ts"> // 添加lang="ts", 里面写ts或js都可以export default {name:'App' //组件名}</script><style>.app {background-color: #ddd;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;}
</style>
总结
  • Vite 项目中,index.html 是项目的入口文件,在项目最外层。
  • 加载index.html后,Vite 解析 <script type="module" src="xxx"> 指向的JavaScript
  • Vue3在main.ts中是通过 createApp 函数创建一个应用实例。

2.3. 一个简单的效果

Vue3向下兼容Vue2语法,且Vue3中的模板中可以没有根标签

Person.vue

<template><div class="person"><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><button @click="changeName">修改名字</button><button @click="changeAge">年龄+1</button><button @click="showTel">点我查看联系方式</button></div>
</template><script lang="ts">export default {name:'App',data() {return {name:'张三',age:18,tel:'13888888888'}},methods:{changeName(){this.name = 'zhang-san'},changeAge(){this.age += 1},showTel(){alert(this.tel)}},}
</script>

App.vue

<template><div class="app"><h1>你好啊!</h1><Person/></div>
</template><script lang="ts">import Person from './components/Person.vue'export default {name:'App', //组件名components:{Person} //注册组件}
</script><style>.app {background-color: #ddd;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;}
</style>

3. Vue3核心语法

3.1. OptionsAPI 与 CompositionAPI

  • Vue2API设计是Options(配置)风格的。
  • Vue3API设计是Composition(组合)风格的。

Options API 的弊端

Options类型的 API,数据、方法、计算属性等,是分散在:datamethodscomputed中的,若想新增或者修改一个需求,就需要分别修改:datamethodscomputed,不便于维护和复用。

在这里插入图片描述
在这里插入图片描述

Composition API 的优势

可以用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。

在这里插入图片描述

在这里插入图片描述

3.2. 拉开序幕的 setup

setup 概述

介绍

  • setupVue3中一个新的配置项,值是一个函数。
  • 它是 Composition API “表演的舞台,组件中所用到的:数据、方法、计算属性、监视…等等,均配置在setup中。

特点如下:

  • setup函数返回的对象中的内容,可直接在模板中使用。
  • setup中访问thisundefined
  • setup函数会在beforeCreate之前调用,它是“领先”所有钩子执行的。
<template><div class="person"><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><button @click="changeName">修改名字</button><button @click="changeAge">年龄+1</button><button @click="showTel">点我查看联系方式</button></div>
</template><script lang="ts">export default {name:'Person',// 生命周期函数beforeCreate(){console.log('beforeCreate')},setup(){// 先打印的setup..., 再打印的beforeCreate, 说明了setup函数与beforeCreate生命周期函数的执行顺序console.log('setup ...')// 【setup函数中的this是undefined】console.log(this); // undefined// 数据,原来写在data中【注意:此时的name、age、tel数据都不是响应式数据】//                   (不是响应式的意思是:当这些数据变化,并不会触发dom更新,//                                     模板中应用这些变量的地方没有重新渲染)let name = '张三'let age = 18let tel = '13888888888'// 方法,原来写在methods中function changeName(){name = 'zhang-san'        // 注意:此时这么修改name页面是不变化的console.log(name)         // (name确实改了,但name不是响应式的)}function changeAge(){age += 1                  // 注意:此时这么修改age页面是不变化的console.log(age)          // (age确实改了,但age不是响应式的)}function showTel(){alert(tel)}// 返回一个对象,对象中的内容,模板中可以直接使用(将数据、方法交出去,模板中才可以使用这些交出去的数据、方法)return {name,age,tel,changeName,changeAge,showTel}}}
</script>

setup 的返回值

  • 若返回一个对象:则对象中的:属性、方法等,在模板中均可以直接使用**(重点关注)。**
  • 若返回一个函数:则可以直接指定 自定义渲染的内容,代码如下:
<template><div class="person">我特么一点都不重要了</div>
</template><script lang="ts">export default {name:'Person',setup(){// setup的返回值也可以是一个渲染函数// (模板什么的都不重要了,直接在页面上渲染成:你好啊!这几个字)// return ()=>'哈哈'}}
</script><style scoped>.person {background-color: skyblue;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;}button {margin: 0 5px;}
</style>

setup 与 Options API 的关系

  • Vue2 的配置(datamethos…)中可以访问到 setup中的属性、方法。
  • 但在setup不能访问到Vue2的配置(datamethos…)。
  • 如果与Vue2冲突,则setup优先。
<template><div class="person"><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="showTel">查看联系方式</button><hr><h2>测试1:{{a}}</h2><h2>测试2:{{c}}</h2><h2>测试3:{{d}}</h2><button @click="b">测试</button></div>
</template><script lang="ts">export default {name:'Person',beforeCreate(){console.log('beforeCreate')},data(){return {a:100,// 在data配置项中, 可以使用this.name来使用setup中交出的数据, 因为setup执行时机更早。// 但是在setup中不能使用在data中定义的数据c:this.name, d:900,age:90}},methods:{b(){console.log('b')}},// setup可以与data、methods等配置项同时存在setup(){// 数据,原来是写在data中的,此时的name、age、tel都不是响应式的数据let name = '张三'let age = 18let tel = '13888888888'// 方法function changeName() {name = 'zhang-san' // 注意:这样修改name,页面是没有变化的console.log(name)  // name确实改了,但name不是响应式的}function changeAge() {age += 1           // 注意:这样修改age,页面是没有变化的console.log(age)   // age确实改了,但age不是响应式的}function showTel() {alert(tel)}// 将数据、方法交出去,模板中才可以使用return {name,age,tel,changeName,changeAge,showTel}// setup的返回值也可以是一个渲染函数// return ()=>'哈哈'}}
</script><style scoped>.person {background-color: skyblue;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;}button {margin: 0 5px;}
</style>

setup 语法糖

setup函数有一个语法糖,这个语法糖,可以让我们把setup独立出去,代码如下:

<template><div class="person"><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><button @click="changName">修改名字</button><button @click="changAge">年龄+1</button><button @click="showTel">点我查看联系方式</button></div></template><!-- 专门单个弄个script标签, 特地来配置组件的名字 -->
<script lang="ts">export default {name:'Person',}
</script><!-- 下面的写法是setup语法糖 -->
<!-- 1. 相当于写了setup函数; 2. 相当于自动把其中定义的变量交出去)-->
<script setup lang="ts">console.log(this) // undefined// 数据(注意:此时的name、age、tel都不是响应式数据)let name = '张三'let age = 18let tel = '13888888888'// 方法function changName(){name = '李四'//注意:此时这么修改name页面是不变化的}function changAge(){console.log(age)age += 1 //注意:此时这么修改age页面是不变化的}function showTel(){alert(tel)}
</script>

扩展:上述代码,还需要编写一个不写setupscript标签,去指定组件名字,比较麻烦,我们可以借助vite中的插件简化

  1. 第一步:npm i vite-plugin-vue-setup-extend -D
  2. 第二步:vite.config.ts
import { fileURLToPath, URL } from 'node:url'import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'// https://vitejs.dev/config/
export default defineConfig({plugins: [vue(),VueSetupExtend(),],resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url))}}
})
  1. 第三步:<script setup lang="ts" name="Person">

3.3. ref 创建:基本类型的响应式数据

  • **作用:**定义响应式变量。
  • 语法:let xxx = ref(初始值)
  • **返回值:**一个RefImpl的实例对象,简称ref对象refref对象的value属性是响应式的
  • 注意点:
    • JS中操作数据需要:xxx.value,但模板中不需要.value,直接使用即可。
    • 对于let name = ref('张三')来说,name不是响应式的,name.value是响应式的。
<template><div class="person"><!-- 模板中直接使用, 不需要.value --><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><h2>电话:{{tel}}</h2><button @click="changeName">修改名字</button><button @click="changeAge">年龄+1</button><button @click="showTel">点我查看联系方式</button></div>
</template><!-- 使用了setup语法糖, 会自动将定义的变量和方法交出去, 以供给模板使用 -->
<script setup lang="ts" name="Person">// 引入vue中的ref函数import { ref } from 'vue'// name和age是一个RefImpl的实例对象,简称ref对象,它们的value属性是响应式的。//(所谓的响应式指的是, 对数据的改变后, 能够让模板中使用该数据的地方得到重新渲染更新)// ref是1个函数, 向这个ref函数中传入参数, 返回的是1个RefImpl的实例对象let name = ref('张三')let age = ref(18)// tel就是一个普通的字符串,不是响应式的let tel = '13888888888'function changeName(){// JS中操作ref对象时候需要.valuename.value = '李四' // 页面得到刷新console.log(name.value)// 注意:name不是响应式的,name.value是响应式的,所以如下代码并不会引起页面的更新。// name = ref('zhang-san')}function changeAge(){// JS中操作ref对象时候需要.valueage.value += 1      // 页面得到刷新console.log(age.value)}function showTel(){// tel是普通数据      tel += '1'          // tel的确改了, 但页面并未刷新alert(tel)}
</script>

3.4. reactive 创建:对象类型的响应式数据

  • 作用:定义一个响应式对象(基本类型不要用它,要用ref,否则报错)
  • 语法:let 响应式对象= reactive(源对象)
  • **返回值:**一个Proxy的实例对象,简称:响应式对象。
  • 注意点:reactive定义的响应式数据是“深层次”的。
<template>
<div class="person"><h2>汽车信息:一台{{ car.brand }}汽车,价值{{ car.price }}万</h2><h2>游戏列表:</h2><ul><li v-for="g in games" :key="g.id">{{ g.name }}</li></ul><h2>测试:{{ obj.a.b.c.d }}</h2><button @click="changeCarPrice">修改汽车价格</button><button @click="changeFirstGame">修改第一游戏</button><button @click="test">测试</button></div>
</template><script lang="ts" setup name="Person">import { reactive } from 'vue'// 定义数据// reactive是1个函数, 向这个reactive函数中传入参数(传入对象或数组), 返回的是1个Proxy的实例对象//(Proxy是原生Js就有的函数)// reactive函数中传入对象let car = reactive({ brand: '奔驰', price: 100 }) console.log('car', car); // car Proxy {brand: '奔驰', price: 100}// reactive函数传入数组let games = reactive([  { id: 'ahsgdyfa01', name: '英雄联盟' },{ id: 'ahsgdyfa02', name: '王者荣耀' },{ id: 'ahsgdyfa03', name: '原神' }])// reactive定义的响应式数据是 深层次 的let obj = reactive({a: {b: {c: {d: 666}}}})// 修改对象中的属性(修改使用reactive包裹对象后返回的对象)function changeCarPrice() {car.price += 10}// 修改数组中的对象的属性(修改使用reactive包裹数组后返回的对象)function changeFirstGame() {games[0].name = '流星蝴蝶剑'}function test() {obj.a.b.c.d = 999}
</script>

3.5. ref 创建:对象类型的响应式数据

  • 其实ref接收的数据可以是:基本类型对象类型
  • ref接收的是对象类型,内部其实也是调用了reactive函数。
<template><div class="person"><h2>汽车信息:一台{{ car.brand }}汽车,价值{{ car.price }}万</h2><h2>游戏列表:</h2><ul><li v-for="g in games" :key="g.id">{{ g.name }}</li></ul><h2>测试:{{ obj.a.b.c.d }}</h2><button @click="changeCarPrice">修改汽车价格</button><button @click="changeFirstGame">修改第一游戏</button><button @click="test">测试</button></div>
</template><script lang="ts" setup name="Person">import { ref,reactive } from 'vue'// 使用ref定义对象类型响应式数据let car = ref({ brand: '奔驰', price: 100 })// 使用reactive定义对象类型响应式数据let car2 = reactive({brand: '奔驰', price: 100})// reactive只能用来定义对象类型的响应式数据// let name = reactive('zhangsan') // 错误, value cannot be made reactive: zhangsan// 使用ref定义对象(数组)类型响应式数据let games = ref([{ id: 'ahsgdyfa01', name: '英雄联盟' },{ id: 'ahsgdyfa02', name: '王者荣耀' },{ id: 'ahsgdyfa03', name: '原神' }])// 使用ref定义对象类型响应式数据也是深层次的let obj = ref({a: {b: {c: {d: 666}}}})// 若ref接收的是对象类型,内部其实也是使用的reactive函数console.log(car)       // RefImpl {__v_isShallow: false, dep: undefined, //          __v_isRef: true, _rawValue: {…}, _value: Proxy}console.log(car.value) // Proxy {brand: '奔驰', price: 100}console.log(car2)      // Proxy {brand: '奔驰', price: 100}function changeCarPrice() {// 使用ref函数定义的响应式数据, 在js操作时, 需要带上.value, 才能碰到内部的Proxy对象car.value.price += 10console.log(car.value.price);}function changeFirstGame() {// 使用ref函数定义的响应式数据, 在js操作时, 需要带上.value, 才能碰到内部的Proxy对象games.value[0].name = '流星蝴蝶剑'console.log(games.value);   // Proxy {0: {…}, 1: {…}, 2: {…}}}function test() {// 使用ref函数定义的响应式数据, 在js操作时, 需要带上.value, 才能碰到内部的Proxy对象obj.value.a.b.c.d = 999}</script>

3.6. ref 对比 reactive

宏观角度

  • ref可以定义:基本类型、对象类型的响应式数据

  • reactive只能定义:对象类型的响应式数据

区别

  • ref创建的变量必须使用.value(可以使用volar插件自动添加.value)。

    可以在齿轮->设置->扩展->volar中勾选在这里插入图片描述
    ,它会在使用ref创建的变量时,自动添加上.value

  • reactive重新分配一个新对象,会失去响应式(可以使用Object.assign去整体替换)。

    <template><div class="person"><h2>汽车信息:一台{{ car.brand }}汽车,价值{{ car.price }}万</h2><button @click="changeBrand">改品牌</button><button @click="changePrice">改价格</button><button @click="changeCar">改car</button></div>
    </template><script lang="ts" setup name="Person">import { ref,reactive } from 'vue'let car = reactive({brand:'奔驰', price:100})function changeBrand() {// 正常修改car的brand, 并且是响应式car.brand = '宝马'
    }function changePrice() {// 正常修改car的price, 并且是响应式car.price += 10
    }function changeCar() {// 错误做法1// 不可以直接给reactive重新分配一个新对象,这会让car直接失去响应式// car = {brand:'奥托', price:10}// 错误做法2// 这样也不行, 因为模板中用的car是上面定义的响应式对象, // 现在car指向的是1个新的响应式对象, 而模板中压根就没有使用这个新的响应式对象// car =  reactive({brand:'奥托', price:10})// 正确做法(car仍然是响应式的)// API介绍: Object.assign(obj1, obj2, obj3, ..), //         将obj2中的每一组属性和值设置到obj1中, 然后obj3的每一组属性和值设置到obj1中Object.assign(car, {brand:'奥托', price:10})
    }</script>
    
    <template><div class="person"><h2>汽车信息:一台{{ car.brand }}汽车,价值{{ car.price }}万</h2><button @click="changeBrand">改品牌</button><button @click="changePrice">改价格</button><button @click="changeCar">改car</button></div>
    </template><script lang="ts" setup name="Person">import { ref,reactive } from 'vue'let car = ref({brand:'奔驰', price:100})function changeBrand() {// 正常修改car的brand, 并且是响应式car.value.brand = '宝马'
    }function changePrice() {// 正常修改car的price, 并且是响应式car.value.price += 10
    }function changeCar() {// 错误做法1// 不能直接给car换了个ref, 因为模板中压根就没有使用这个新的RefImpl对象// car = ref({brand:'奥托', price:10})// 正确做法1(car仍然是响应式的)// API介绍: Object.assign(obj1, obj2, obj3, ..), 将obj2中的每一组属性和值设置到obj1中, //         然后obj3的每一组属性和值设置到obj1中// Object.assign(car.value, {brand:'奥托', price:10})// 正确做法2//(这里相比于对car使用reactive定义而言, 使用ref定义则可以直接给car.value整体赋值// 原因在于car.value获取的是Proxy响应式对象, 凡是对Proxy响应式对象的操作都可以被拦截到)car.value = {brand:'奥托', price:10}}</script>
    

使用原则

  • 若需要一个基本类型的响应式数据,必须使用ref

  • 若需要一个响应式对象,层级不深,refreactive都可以。

  • 若需要一个响应式对象,且层级较深,推荐使用reactive

3.7 toRefs 与 toRef

  • 作用:将一个响应式对象中的每一个属性,转换为ref对象。
  • 备注:toRefstoRef功能一致,但toRefs可以批量转换。

现象

对响应式对象直接结构赋值,得到的数据不是响应式的

<template><div class="person"><h2>姓名:{{ person.name }} {{ name }}</h2><h2>年龄:{{ person.age }}  {{ age }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button></div>
</template><script lang="ts" setup name="Person2">import { ref, reactive, toRefs, toRef } from 'vue'// 数据let person = reactive({ name: '张三', age: 18 })console.log(person);                // Proxy {name: '张三', age: 18}// 这里的解构赋值其实就等价于: let name = person.name; let age = person.age;// 只是记录了此时person.name、person.age的值, 仅此而已// 因此, 此处使用结构赋值语法获取的name和age都不是响应式的let {name, age } = personconsole.log(name, age);              // 张三 18// 方法function changeName() {name += '~'console.log(name, person.name);  // 变化的是name, 而person.name仍然未修改}function changeAge() {age += 1console.log(age, person.age);    // 变化的是age, 而person.age仍然未修改}</script>

toRefs&toRef的使用

通过toRefs将person对象中的所有属性都批量取出, 且依然保持响应式的能力

<template>
<div class="person"><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><h2>性别:{{ person.gender }}  {{ gender }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changeGender">修改性别</button><button @click="changeGender2">修改性别2</button></div>
</template><script lang="ts" setup name="Person">import { ref, reactive, toRefs, toRef } from 'vue'// 数据let person = reactive({ name: '张三', age: 18, gender: '男' })// 通过toRefs将person对象中的所有属性都批量取出, 且依然保持响应式的能力//(使用toRefs从person这个响应式对象中,解构出name、age, 且name和age依然是响应式的,//  name和gender的值是ref类型, 其value值指向的是person.name和person.age,//  对name.value和对age.value的修改将会修改person.name和person.age, 并且会页面渲染刷新)let { name, age } = toRefs(person)console.log(name.value, name);     // '张三' ObjectRefImpl {_object: Proxy, _key: 'name', //                 _defaultValue: undefined, __v_isRef: true}console.log(age.value, age.value); // 18 ObjectRefImpl {_object: Proxy, _key: 'age', //                   _defaultValue: undefined, __v_isRef: true}console.log(toRefs(person));       // {name: ObjectRefImpl, age: ObjectRefImpl, //  gender: ObjectRefImpl}// 通过toRef将person对象中的gender属性取出,且依然保持响应式的能力let gender = toRef(person, 'gender')console.log(gender, gender.value); // ObjectRefImpl {_object: Proxy, _key: 'gender', //               _defaultValue: undefined, __v_isRef: true} '男'// 方法function changeName() {// 此处修改name.value, 将会修改person.name, 并且页面会刷新person.name的值name.value += '~'console.log(name.value, person.name);}function changeAge() {// 此处修改age.value, 将会修改person.age, 并且页面会刷新person.age的值age.value += 1console.log(age.value, person.age);}function changeGender() {// 此处修改gender.value, 将会修改person.age, 并且页面会刷新person.gender的值gender.value = '女'console.log(gender.value, person.gender);}function changeGender2() {// 此处对person.gender的修改, 将会修改上面的let gender = toRef(person, 'gender')// 并且页面会刷新person.gender和gender的值person.gender = '男'console.log(gender.value, person.gender);}
</script>

3.8 computed

作用:根据已有数据计算出新数据(和Vue2中的computed作用一致)。

<template><div class="person">姓:<input type="text" v-model="firstName"> <br>名:<input type="text" v-model="lastName"> <br>全名:<span>{{ fullName }}</span> <br><button @click="changeFullName">全名改为: li-si</button></div>
</template><script setup lang="ts" name="App">// 引入computed计算属性函数
import { ref, computed } from 'vue'let firstName = ref('zhang')
let lastName = ref('san')// 计算属性——只读取,不修改
/* 
// 1. 使用时, 在computed中传入1个函数。在模板中, 直接使用计算属性即可。
// 2. 当计算属性依赖的数据只要发生变化, 它就会重新计算, 如果页面中有使用到该计算属性, 那么就会重新渲染模板 
// 3. 只会计算1次, 后面会使用缓存, 而方法是没有缓存的
let fullName = computed(()=>{return firstName.value + '-' + lastName.value
}) 
console.log(fullName); // ComputedRefImpl {dep: undefined, __v_isRef: true, //                  __v_isReadonly: true, effect: ReactiveEffect, _setter: ƒ, …}*/// 计算属性——既读取又修改
let fullName = computed({// 读取get() {// 当firstName或lastName变化时, 计算属性会重新计算, 并刷新页面渲染return firstName.value + '-' + lastName.value},// 修改// 当修改计算属性时(或者说给计算属性赋值时, 注意要.value), 此方法会被调用set(val) {console.log('有人修改了fullName', val)firstName.value = val.split('-')[0]lastName.value = val.split('-')[1]}
})function changeFullName() {// 修改fullName计算属性(会触发计算属性中set方法的调用)fullName.value = 'li-si'
}
</script>

3.9 watch

作用

监视数据的变化(和Vue2中的watch作用一致)

特点

Vue3中的watch只能监视以下四种数据

  • ref定义的数据。

  • reactive定义的数据。

  • 函数返回一个值(getter函数,所谓的getter函数就是能返回一个值的函数)。

  • 一个包含上述内容的数组。

场景

我们在Vue3中使用watch的时候,通常会遇到以下几种情况:

* 情况一

监视ref定义的【基本类型】数据:直接写数据名即可,监视的是其value值的改变。

<template><div class="person"><h1>情况一:监视【ref】定义的【基本类型】数据</h1><h2>当前求和为:{{ sum }}</h2><button @click="changeSum">点我sum+1</button></div>
</template><script lang="ts" setup name="Person">// 引入watch监视函数
import { ref, watch } from 'vue'// 数据
let sum = ref(0)// 方法
function changeSum() {sum.value += 1
}// 监视,情况一:监视【ref】定义的【基本类型】数据
//(注意:这里监视写的是sum, 而不是sum.value哦)
const stopWatch = watch(sum, (newValue, oldValue) => {console.log('sum变化了', newValue, oldValue) // 注意: 这里也没带.value哦if (newValue >= 10) {// 解除监视(即: 当调用此方法后, 不会再监视sum的变化了, 也就是当sum变化时, 当前的监视函数不再执行了)stopWatch()}
})</script><style scoped>
...
</style>
* 情况二

监视ref定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】。若想监视对象内部的数据,要手动开启深度监视。

注意:

  • 若修改的是ref定义的对象中的属性,newValueoldValue 都是新值,因为它们是同一个对象。

  • 若修改整个ref定义的对象,newValue 是新值, oldValue 是旧值,因为不是同一个对象了。

示例1
<template><div class="person"><h1>情况二:监视【ref】定义的【对象类型】数据</h1><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changePerson">修改整个人</button></div>
</template><script lang="ts" setup name="Person">import { ref, watch } from 'vue'// 数据
let person = ref({name: '张三',age: 18
})// 方法
function changeName() {person.value.name += '~'   // 当修改person.value.name时, 监视函数未被触发
}function changeAge() {person.value.age += 1      // 当修改person.value.age时, 监视函数也未被触发
}function changePerson() {person.value = { name: '李四', age: 90 }  // 当整体修改person.value时, 此时监视函数被触发
}                                            // (因为监视的是对象的地址值, 所以这里每次修改都会触发监视函数)/* 监视,情况一:监视【ref】定义的【对象类型】数据,监视的是对象的地址值。watch的第一个参数是:被监视的数据watch的第二个参数是:监视的回调
*/
watch(person, (newValue, oldValue) => {console.log('person变化了', newValue, oldValue)// 一直调用changePerson方法, 控制台如下输出// person变化了 Proxy {name: '李四', age: 90} Proxy {name: '张三', age: 18}// person变化了 Proxy {name: '李四', age: 90} Proxy {name: '李四', age: 90}// person变化了 Proxy {name: '李四', age: 90} Proxy {name: '李四', age: 90}// person变化了 Proxy {name: '李四', age: 90} Proxy {name: '李四', age: 90}// ...
})</script><style scoped>
...
</style>
示例2
<template><div class="person"><h1>情况二:监视【ref】定义的【对象类型】数据</h1><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changePerson">修改整个人</button></div>
</template><script lang="ts" setup name="Person">import { ref, watch } from 'vue'// 数据
let person = ref({name: '张三',age: 18
})// 方法
function changeName() {person.value.name += '~'// 因为开启了深度监视, 当修改person.value.name时, 监视函数被触发
}                         //(但由于原对象并未修改, 所以监视函数中输出的newVal和oldVal是一样的)// 每次调用changeName都修改, 变化如下:// person变化了 Proxy {name: '张三~', age: 18} Proxy {name: '张三~', age: 18}// person变化了 Proxy {name: '张三~~', age: 18} Proxy {name: '张三~~', age: 18}// person变化了 Proxy {name: '张三~~', age: 18} Proxy {name: '张三~~', age: 18}// ...
function changeAge() {person.value.age += 1   // 因为开启了深度监视, 当修改person.value.name时, 监视函数被触发
}                           //(但由于原对象并未修改, 所以监视函数中输出的newVal和oldVal是一样的)// 每次调用changeName都修改, 变化如下:// person变化了 Proxy {name: '张三', age: 19} Proxy {name: '张三', age: 19}// person变化了 Proxy {name: '张三', age: 20} Proxy {name: '张三', age: 20}// person变化了 Proxy {name: '张三', age: 21} Proxy {name: '张三', age: 21}// ...function changePerson() {person.value = { name: '李四', age: 90 }// 当整体修改person.value时, 监视函数被触发//(但由于原对象都改了, 所以监视函数中输出的newVal和oldVal是不一样的)// 每次调用changeName都修改, 变化如下:
}                           // person变化了 Proxy {name: '李四', age: 90} Proxy {name: '张三', age: 18}// person变化了 Proxy {name: '李四', age: 90} Proxy {name: '李四', age: 90}// person变化了 Proxy {name: '李四', age: 90} Proxy {name: '李四', age: 90}// ...
/* 监视,情况二:监视【ref】定义的【对象类型】数据,监视的是对象的地址值,若想监视对象内部属性的变化,需要手动开启深度监视watch的第一个参数是:被监视的数据watch的第二个参数是:监视的回调watch的第三个参数是:配置对象(deep、immediate等等) 
*/
watch(person, (newValue, oldValue) => {console.log('person变化了', newValue, oldValue)
}, { deep: true, immediate: true })</script><style scoped>
.person {background-color: skyblue;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}button {margin: 0 5px;
}li {font-size: 20px;
}
</style>
* 情况三

监视reactive定义的【对象类型】数据,且默认开启了深度监视。

<template>
<div class="person"><h1>情况三:监视【reactive】定义的【对象类型】数据</h1><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changePerson">修改整个人</button><hr><h2>测试:{{obj.a.b.c}}</h2><button @click="test">修改obj.a.b.c</button></div>
</template><script lang="ts" setup name="Person">import {reactive,watch} from 'vue'// 数据let person = reactive({name:'张三',age:18})let obj = reactive({a:{b:{c:666}}})// 方法function changeName(){person.name += '~'// 每次调用changeName都修改, 变化如下:// person变化了 Proxy {name: '张三~', age: 18} Proxy {name: '张三~', age: 18}// person变化了 Proxy {name: '张三~~', age: 18} Proxy {name: '张三~~', age: 18}// person变化了 Proxy {name: '张三~~~', age: 18} Proxy {name: '张三~~~', age: 18}// ...//(如上结果, //  1. 证明监视到了person的name //  2. oldVal和newVal是一样的输出, 是因为虽然监测到person的变化, 但oldVal和newVal是同一对象, 从这来说并未改变)}function changeAge(){person.age += 1// 每次调用changeAge都修改, 变化如下:// person变化了 Proxy {name: '张三', age: 19} Proxy {name: '张三', age: 19}// person变化了 Proxy {name: '张三', age: 20} Proxy {name: '张三', age: 20}// person变化了 Proxy {name: '张三', age: 21} Proxy {name: '张三', age: 21}// ...//(如上结果, //  1. 证明监视到了person的age//  2. oldVal和newVal是一样的输出, 是因为虽然监测到person的变化, 但oldVal和newVal是同一对象, 从这来说并未改变)}function changePerson(){// 此处注意: 使用reactive函数定义的数据, 不能直接替换, 可以如下方式对person中的属性做批量修改 Object.assign(person,{name:'李四',age:80})// 多次调用changePerson, 仅有1次监视到到修改, 变化如下:// person变化了 Proxy {name: '李四', age: 80} Proxy {name: '李四', age: 80}//(如上结果, //  1. 证明监视到了person的name和age的改变//  2. oldVal和newVal是一样的输出, 是因为虽然监测到person的变化, 但oldVal和newVal仍是同一对象, 从这来说并未改变)}function test(){obj.a.b.c = 888// 此处证明watch监控reactive定义的对象类型数据, 默认是开启了深度监视的}// 监视,情况三:监视【reactive】定义的【对象类型】数据,且默认是开启深度监视的(隐式创建了深层次的监听, 无法关闭)watch(person,(newValue,oldValue)=>{console.log('person变化了',newValue,oldValue)})watch(obj,(newValue,oldValue)=>{console.log('Obj变化了',newValue,oldValue)})</script><style scoped>
...
</style>
* 情况四

监视refreactive定义的【对象类型】数据中的某个属性,注意点如下:

  1. 若该属性值不是【对象类型】,需要写成函数形式。
  2. 若该属性值是依然是【对象类型】,可直接编,也可写成函数,建议写成函数。

结论:监视的要是对象里的属性,那么最好写函数式。(注意点:若是对象,监视的是地址值;需要关注对象内部,则需要手动开启深度监视。)

没有监视的代码
<template>
<div class="person"><h1>情况四:监视【ref】或【reactive】定义的【对象类型】数据中的某个属性</h1><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changeC1">修改第一台车</button><button @click="changeC2">修改第二台车</button><button @click="changeCar">修改整个车</button></div>
</template><script lang="ts" setup name="Person">import { reactive, watch } from 'vue'// 数据let person = reactive({name: '张三',age: 18,car: {c1: '奔驰',c2: '宝马'}})// 方法function changeName() {person.name += '~'}function changeAge() {person.age += 1}function changeC1() {person.car.c1 = '奥迪'}function changeC2() {person.car.c2 = '大众'}function changeCar() {// 注意此处: 因为person是使用reactive定义的, 所以person整体不能改(改是可以改, 但是不再响应式了, //                                                         所以说不能整体直接改), //          但是person里面的car属性可以改, 因此可以如下改person.car = { c1: '雅迪', c2: '爱玛' }}</script><style scoped>...
</style>
监视reactive定义的对象类型中的某个基本属性
<template><div class="person"><h1>情况四:监视【ref】或【reactive】定义的【对象类型】数据中的某个属性</h1><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changeC1">修改第一台车</button><button @click="changeC2">修改第二台车</button><button @click="changeCar">修改整个车</button></div>
</template><script lang="ts" setup name="Person">import { reactive, watch } from 'vue'// 数据let person = reactive({name: '张三',age: 18,car: {c1: '奔驰',c2: '宝马'}})// 方法function changeName() {person.name += '~'// 一直调用changeName方法, 控制台如下输出// person.name变化了 张三~ 张三// person.name变化了 张三~~ 张三~// person.name变化了 张三~~~ 张三~~// ...}function changeAge() {person.age += 1}function changeC1() {person.car.c1 = '奥迪'}function changeC2() {person.car.c2 = '大众'}function changeCar() {// 注意此处: 因为person是使用reactive定义的, 所以person整体不能改(改是可以改, 但是不再响应式了, //                                                         所以说不能整体直接改), //          但是person里面的car属性可以改, 因此可以如下改person.car = { c1: '雅迪', c2: '爱玛' }}// 监视,情况四:监视响应式对象中的某个属性,且该属性是基本类型的,要写成函数式(不能直接写person.name哦)//(如下监视, 将会只监视person的name属性的变化, //  当person的name属性发生变化时, 将会触发监听函数执行, 其它属性变化不会触发监听函数的执行)watch(()=> person.name,(newValue,oldValue)=>{console.log('person.name变化了',newValue,oldValue)}) // 错误写法, 因为person的name属性是基本类型, 所以不能直接写为第1个参数, 应该要用函数包一下/*watch(person.name,(newValue,oldValue)=>{console.log('person.name变化了',newValue,oldValue)})*/// 监视person的car属性中的c1属性//(当调用changeC1方法时, 此处能够监测到person.car.c1的改变;//  多次调用changeC1方法, 此处只监测到了1次, 因为后面都没改person.car.c1的值;//  当调用changeCar方法, 此处能够监测到person.car.c1的改变;//  多次调用changeCar方法, 此处只监测到了1次, 因为后面都没改person.car.c1的值;)watch(()=> person.car.c1,(newValue,oldValue)=>{console.log('person.car.c1变化了',newValue,oldValue)})// 错误写法, 因为person的car.c1属性是基本类型, 所以不能直接写为第1个参数, 应该要用函数包一下/*watch(person.car.c1,(newValue,oldValue)=>{console.log('person.car.c1变化了',newValue,oldValue)})*/</script><style scoped>
...
</style>
监视reactive定义的对象类型中的某个对象属性
<template><div class="person"><h1>情况四:监视【ref】或【reactive】定义的【对象类型】数据中的某个属性</h1><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changeC1">修改第一台车</button><button @click="changeC2">修改第二台车</button><button @click="changeCar">修改整个车</button></div>
</template><script lang="ts" setup name="Person">import { reactive, watch } from 'vue'// 数据let person = reactive({name: '张三',age: 18,car: {c1: '奔驰',c2: '宝马'}})// 方法function changeName() {person.name += '~'}function changeAge() {person.age += 1}function changeC1() {person.car.c1 = '奥迪'}function changeC2() {person.car.c2 = '大众'}function changeCar() {// 注意此处: 因为person是使用reactive定义的, 所以person整体不能改(改是可以改, 但是不再响应式了, //                                                             所以说不能整体直接改), //          但是person里面的car属性可以改, 因此可以如下改person.car = { c1: '雅迪', c2: '爱玛' }}// 监视,情况四:监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,更推荐写函数// 建议写成函数的形式// 当调用changeC1或changeC2方法时, 会触发此处的监测函数执行// 当调用changeCar方法时, 会触发此处的监测函数执行// 【最佳实践】(函数式来开启对person.car的地址值的监测, 然后deep:true开启对该对象的深度监视)watch(() => person.car, (newValue, oldValue) => {console.log('person.car变化了', newValue, oldValue)}, { deep: true })// 如果写成下面这样, 监测的其实是person.car的地址值, 只有在person.car整体改变时, 才会触发此处的监测函数执行//  当调用changeC1或changeC2方法时, 不会触发此处的监测函数执行/* watch(() => person.car, (newValue, oldValue) => {console.log('person.car变化了', newValue, oldValue)}) */// 如果写成下面这样(直接写的做法), 那么当调用changeCar方法时, 不会触发此处的监测函数执行// 当调用changeC1或changeC2方法时, 会触发此处的监测函数执行//(因为person.car是person中的对象类型属性, 因此这里可以直接写)/* watch(person.car, (newValue, oldValue) => {console.log('person.car变化了', newValue, oldValue)}) */</script><style scoped>...
</style>
* 情况五

监视上述的多个数据

<template>
<div class="person"><h1>情况五:监视上述的多个数据</h1><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changeC1">修改第一台车</button><button @click="changeC2">修改第二台车</button><button @click="changeCar">修改整个车</button></div>
</template><script lang="ts" setup name="Person">import {reactive,watch} from 'vue'// 数据let person = reactive({name:'张三',age:18,car:{c1:'奔驰',c2:'宝马'}})// 方法function changeName(){person.name += '~'}function changeAge(){person.age += 1}function changeC1(){person.car.c1 = '奥迪'}function changeC2(){person.car.c2 = '大众'}function changeCar(){person.car = {c1:'雅迪',c2:'爱玛'}}// 监视,情况五:监视上述的多个数据//(person.name是基本类型, 所以要写成函数式; person.car是对象类型, 所以可以直接写;// 这里的newVal和oldVal都是数组, 跟监视的2个源相对应; // deep开启深度监视, 不止可以监视地址值, 还包括内部属性的变化;)watch([()=>person.name, person.car],(newValue, oldValue)=>{console.log('person.car变化了',newValue,oldValue)},{deep:true})</script><style scoped>
...
</style>