Vue3组合式API中watch传值生命周期与自定义Hook实战

文章目录

  • 前言
  • [一、 watch 监视的 5 种情况](#一、 watch 监视的 5 种情况)
    • [情况 1:监视 ref 定义的【基本类型】数据](#情况 1:监视 ref 定义的【基本类型】数据)
    • [情况 2:监视 ref 定义的【对象类型】数据](#情况 2:监视 ref 定义的【对象类型】数据)
    • [情况 3:监视 reactive 定义的【对象类型】数据](#情况 3:监视 reactive 定义的【对象类型】数据)
    • [情况 4:监视 ref 或 reactive 定义的对象中的【某个属性】](#情况 4:监视 ref 或 reactive 定义的对象中的【某个属性】)
    • [情况 5:监视上述多个数据源的【组合】](#情况 5:监视上述多个数据源的【组合】)
  • [二、 watchEffect 高级监听器](#二、 watchEffect 高级监听器)
    • [2.1 什么是 watchEffect?](#2.1 什么是 watchEffect?)
    • [2.2 watch 与 watchEffect 的对比](#2.2 watch 与 watchEffect 的对比)
    • [2.3 代码示例](#2.3 代码示例)
  • [三、 标签的 ref 属性](#三、 标签的 ref 属性)
    • [3.1 用在普通 HTML DOM 标签上](#3.1 用在普通 HTML DOM 标签上)
    • [3.2 用在组件标签(Component)上](#3.2 用在组件标签(Component)上)
  • [四、 父子组件传值(父传子:Props)](#四、 父子组件传值(父传子:Props))
    • [4.1 父组件:通过属性绑定传递数据](#4.1 父组件:通过属性绑定传递数据)
    • [4.2 子组件:使用 `defineProps` 接收数据](#4.2 子组件:使用 defineProps 接收数据)
  • [五、 生命周期函数(Lifecycle Hooks)](#五、 生命周期函数(Lifecycle Hooks))
    • [5.1 概念](#5.1 概念)
    • [5.2 Vue 2 选项式与 Vue 3 组合式 API 对照](#5.2 Vue 2 选项式与 Vue 3 组合式 API 对照)
    • [5.3 组合式 API 生命周期用法示例](#5.3 组合式 API 生命周期用法示例)
  • [六、 自定义 Hook (逻辑复用)](#六、 自定义 Hook (逻辑复用))
    • [6.1 什么是 hook?](#6.1 什么是 hook?)
    • [6.2 规范与设计](#6.2 规范与设计)
    • [6.3 简单实战:封装一个管理累加功能的 Hook](#6.3 简单实战:封装一个管理累加功能的 Hook)
  • 结语

前言

Vue 3 组合式 API 让组件逻辑更加清晰可复用。本文从 watch 五种监视场景到父子组件传值,再到生命周期与自定义 Hook,系统梳理实际开发中最常用的核心技巧。

一、 watch 监视的 5 种情况

在 Vue 3 中,watch 用于监视数据的变化 。watch 只能监视以下四种数据源:ref 定义的数据、reactive 定义的数据、一个函数(getter)、或者包含上述内容的数组 。在实际开发中通常分为以下 5 种使用场景 :

情况 1:监视 ref 定义的【基本类型】数据

特点 :直接写数据名即可,监视的是其 .value 值的改变 。

js 复制代码
<script lang="ts" setup>
import { ref, watch } from 'vue'

let sum = ref(0) 

function changeSum() {
  sum.value += 1 
}

// 监视基本类型:无需加 .value
const stopWatch = watch(sum, (newValue, oldValue) => {
  console.log('sum变化了', newValue, oldValue) 
  if (newValue >= 10) {
    stopWatch() // 显式调用可停止监视 
  }
})
</script>

情况 2:监视 ref 定义的【对象类型】数据

特点 :直接写数据名,默认监视的是对象的地址值 。若想监视对象内部数据的变化,需要手动开启 deep: true(深度监视)

  • 注意

    1. 若修改的是对象内部的属性,newValueoldValue 是完全相同的(因为它们指向内存中的同一个对象) 。
    2. 若修改了整个对象(替换了地址),newValue 为新对象,oldValue 为旧对象 。
js 复制代码
<script lang="ts" setup>
import { ref, watch } from 'vue'

let person = ref({ name: '张三', age: 18 }) 

// 开启深度监视,内部属性变化也能检测到 
watch(person, (newValue, oldValue) => {
  console.log('person变化了', newValue, oldValue) 
}, { deep: true }) 
</script>

情况 3:监视 reactive 定义的【对象类型】数据

特点 :监视 reactive 定义的对象时,默认自动开启了深度监视 ,并且配置 deep: false 是无效的 。同样,回调中的 newValueoldValue 指向同一个对象,二者相等 。

代码段

js 复制代码
<script lang="ts" setup>
import { reactive, watch } from 'vue'

let person = reactive({ name: '张三', age: 18 }) 

// 默认开启深度监视 
watch(person, (newValue, oldValue) => {
  console.log('reactive person变化了', newValue, oldValue) [cite: 25]
})
</script>

情况 4:监视 ref 或 reactive 定义的对象中的【某个属性】

特点 :要监视对象里的某一个特定属性,最好写成 Getter 函数形式(即一个返回值的箭头函数) 。

  1. 若该属性值是基本类型:必须写成函数形式 。
  2. 若该属性值是对象类型 :可以直接写,也可以写成函数,更推荐写成函数 。若写成函数式且关注该属性内部的变化,需要手动开启 { deep: true }
js 复制代码
<script lang="ts" setup>
import { reactive, watch } from 'vue'

let person = reactive({
  name: '张三',
  car: { c1: '奔驰', c2: '宝马' } [cite: 25]
})

// 1. 监视基本类型属性:必须写成函数式 
watch(() => person.name, (newValue, oldValue) => {
  console.log('person.name变化了', newValue, oldValue) [cite: 25]
})

// 2. 监视对象类型属性:写成函数式并手动开启深度监视 
watch(() => person.car, (newValue, oldValue) => {
  console.log('person.car变了', newValue, oldValue) [cite: 26]
}, { deep: true }) [cite: 26]
</script>

情况 5:监视上述多个数据源的【组合】

特点 :第一个参数传入一个数组,数组中包含你想同时监视的数据源 。

js 复制代码
<script lang="ts" setup>
import { reactive, watch } from 'vue'

let person = reactive({ name: '张三', car: { c1: '奔驰' } }) [cite: 25, 27]

// 同时监视名字和车子的对象 [cite: 26]
watch([() => person.name, person.car], (newValue, oldValue) => {
  console.log('数据源组合发生了变化', newValue, oldValue) [cite: 26]
}, { deep: true }) [cite: 26]
</script>

二、 watchEffect 高级监听器

2.1 什么是 watchEffect?

watchEffect立即运行一个函数,同时响应式地追踪其内部使用到的依赖(属性),并在依赖更改时自动重新执行该函数 。

2.2 watch 与 watchEffect 的对比

  1. 相同点:都能监听响应式数据的变化并执行逻辑 。

  2. 不同点

    • watch:必须明确指出要监视的每一个数据源 。

    • watchEffect不用明确指出监视的数据 。它的函数里用到哪些属性,Vue 就会自动追踪并监视哪些属性,非常智能和简练 。

2.3 代码示例

js 复制代码
<script lang="ts" setup>
import { ref, watchEffect } from 'vue'

let temp = ref(0)
let height = ref(0) 

// 自动追踪:函数内部用到了 temp.value 和 height.value,所以这两个变量变化时都会触发该函数 [cite: 27, 28]
watchEffect(() => {
  if (temp.value >= 50 || height.value >= 20) { 
    console.log('水温达到50℃,或水位达到20cm,立刻联系服务器!') 
  }
})
</script>

三、 标签的 ref 属性

在 Vue 3 中,标签上的 ref 属性用于注册模板引用

3.1 用在普通 HTML DOM 标签上

  • 作用 :获取的是对应的真实 DOM 节点 ,用于替代传统的 document.getElementById
js 复制代码
<template>
  <h1 ref="title">前端开发</h1> [cite: 29]
  <button @click="showLog">打印节点</button> [cite: 29]
</template>

<script lang="ts" setup>
import { ref } from 'vue'

// 2. 声明一个名字和 template 中 ref 完全一致的变量
let title = ref() [cite: 29]

function showLog() {
  // 3. 在挂载后通过 .value 拿到真正的 DOM 节点 [cite: 29]
  console.log(title.value) [cite: 29]
}
</script>

3.2 用在组件标签(Component)上

  • 作用 :获取的是该组件的实例对象

  • 安全性限制(核心注意点) :在 Vue 3 的 <script setup> 模式下,子组件内部的数据默认是封闭(私有)的 。父组件即使通过 ref 拿到了子组件实例,默认也无法读取子组件的数据或方法 。

  • 解决方案 :子组件必须使用 defineExpose 宏显式地把允许外部访问的内容暴露出去 。

js 复制代码
<template>
  <Person ref="ren"/> [cite: 29]
  <button @click="test">测试读取</button> [cite: 29]
</template>

<script lang="ts" setup>
import Person from './components/Person.vue' [cite: 29]
import { ref } from 'vue'

let ren = ref() [cite: 29]

function test() {
  console.log(ren.value.name) // 成功拿到:张三 
  console.log(ren.value.age)  // 成功拿到:18 
}
</script>
js 复制代码
<script lang="ts" setup>
import { ref, defineExpose } from 'vue'

let name = ref('张三')
let age = ref(18) 

// 必须通过 defineExpose 显式暴露给父组件 
defineExpose({ name, age }) 
</script>

四、 父子组件传值(父传子:Props)

Props 是实现父组件向子组件传递数据的最基础、最标准的手段 。

4.1 父组件:通过属性绑定传递数据

js 复制代码
<template>
  <Person :list="persons"/> 
</template>

<script lang="ts" setup>
import Person from './components/Person.vue' 
import { reactive } from 'vue'

let persons = reactive([
  { id: '01', name: '张三', age: 18 },
  { id: '02', name: '李四', age: 19 } 
])
</script>

4.2 子组件:使用 defineProps 接收数据

defineProps 是 Vue 3 的编译器宏,不需要单独 import 引入 。它支持三种常见的接收写法 :

js 复制代码
<script lang="ts" setup>
import { defineProps } from 'vue' // 可省,不引入也能直接用 

// 写法一:仅接收(通过数组形式接收) 
// const props = defineProps(['list']) 

// 写法二:接收 + 限制类型(使用 TypeScript 泛型限制) 
// defineProps<{ list: any[] }>() 

// 写法三:接收 + 限制类型 + 指定默认值(使用 withDefaults 宏,生产推荐) 
let props = withDefaults(defineProps<{ list?: any[] }>(), {
  list: () => [{ id: 'default01', name: '小猪佩奇', age: 5 }] 
})

console.log(props.list) // 在 JS/TS 中需要通过 props.xxx 访问 
</script>

<template>
  <div>
    <li v-for="item in list" :key="item.id">
      {{ item.name }} -- {{ item.age }} 
    </li>
  </div>
</template>

五、 生命周期函数(Lifecycle Hooks)

5.1 概念

Vue 组件实例在创建到销毁的过程中,会在特定的时机自动调用一些特定的函数,这些函数统称为生命周期钩子

5.2 Vue 2 选项式与 Vue 3 组合式 API 对照

组件生命周期整体分为四个阶段:创建、挂载、更新、卸载/销毁,每个阶段一前一后各有两个钩子 。

阶段 Vue 2 选项式 API Vue 3 组合式 API (导入使用) 说明与核心作用
创建 beforeCreate / created 不需要专门的钩子 直接写在 setup 脚本中即可(领先于所有钩子执行) 。
挂载 beforeMount onBeforeMount 模板编译完毕,即将挂载到页面 。
挂载 mounted onMounted 极其常用! 页面真实的 DOM 挂载完毕 。通常用于发送网络请求、初始化图表、绑定外部原生 DOM 事件。
更新 beforeUpdate onBeforeUpdate 响应式数据发生了改变,但页面尚未开始重新渲染。
更新 updated onUpdated 数据改变导致的页面 DOM 重新渲染已完成 。
卸载 beforeDestroy onBeforeUnmount 极其常用! 组件即将被卸载、销毁之前 。用于清除定时器、解绑全局自定义事件,防止内存泄漏。
卸载 destroyed onUnmounted 组件已经彻底卸载 。

5.3 组合式 API 生命周期用法示例

js 复制代码
<script lang="ts" setup>
import { onMounted, onUpdated, onBeforeUnmount } from 'vue' 

// 挂载完毕
onMounted(() => {
  console.log('组件挂载成功了,可以发请求了!') 
})

// 卸载之前
onBeforeUnmount(() => {
  console.log('组件即将销毁,正在做收尾和清理定时器的工作...') 
})
</script>

六、 自定义 Hook (逻辑复用)

6.1 什么是 hook?

Vue 3 中的 hook 本质是一个普通的函数 。它把 setup 函数中使用的组合式 API(比如 refreactivecomputed、生命周期钩子等)进行了高度分块封装 。

6.2 规范与设计

  1. 模块文件通常存放在项目的 src/hooks/ 目录下 。
  2. 文件名和函数名规范以 use 开头(例如:useSum.tsuseMouse.ts)。
  3. 在 Hook 内部定义独立的响应式数据和业务方法,并最终将外部需要用到的数据、方法通过 return 暴露出来。

6.3 简单实战:封装一个管理累加功能的 Hook

  • 步骤 1:抽取封装逻辑 (src/hooks/useSum.ts)
typescript 复制代码
import { ref, onMounted } from 'vue'

export default function useSum() {
  // 1. Hook 内部的响应式数据
  let sum = ref(0)

  // 2. Hook 内部的方法
  function add() {
    sum.value += 1
  }

  // 3. 也可以配合生命周期钩子
  onMounted(() => {
    console.log('useSum hook 伴随组件挂载而执行')
  })

  // 4. 向外暴露组件需要的东西
  return { sum, add }
}
  • 步骤 2:在任意组件中优雅复用 (Person.vue)
js 复制代码
<template>
  <div class="person">
    <h2>当前求和为:{{ sum }}</h2>
    <button @click="add">点我加 1</button>
  </div>
</template>

<script lang="ts" setup>
// 引入自定义的 hook
import useSum from '@/hooks/useSum'

// 调用 hook 并解构出里面的响应式数据和方法,逻辑极度清晰且高内聚
const { sum, add } = useSum()
</script>

结语

掌握 watch 与 watchEffect 的差异、defineExpose 的显式暴露机制,以及自定义 Hook 的封装规范,是写出高内聚、低耦合 Vue 3 组件的关键。组合式 API 的精髓在于让逻辑回归功能本身。

相关推荐
海市公约21 小时前
Vue3组合式API与响应式系统核心机制详解
vue3·computed·reactive·ref·响应式系统·composition api·script setup
小茴香3532 天前
Vue3路由权限动态管理
前端·前端框架·vue3
亚林瓜子6 天前
AWS S3日志桶常用过期文件生命周期策略
云计算·生命周期·aws·s3·过期·glacier
暗冰ཏོ6 天前
《2026 Vue2 + Vue3 完整学习指南:基础语法、路由缓存、登录拦截、项目实战与面试题》
前端·vue.js·vue·vue3·vue2
曲幽7 天前
写页面时别再把 Element Plus 整个搬进来啦!Vue3按需加载的坑我帮你踩平了
vue3·web·vite·icon·element plus·vs code·import·unplugin
小云小白8 天前
若依-vue3 把深色版本改成天蓝色-含登录页
vue3·若依·天蓝色
曲幽9 天前
FastApiAdmin 后端接口开发好了,前端管理界面怎么调用与显示?
python·vue3·api·fastapi·web·ant design·view·menu·frontend
曲幽12 天前
我用了FastApiAdmin后,连夜把踩过的坑都整理出来了
redis·python·postgresql·vue3·fastapi·web·sqlalchemy·admin·fastapiadmin
Liu.77413 天前
vue3bug收录
vue3