欢迎光临
我们一直在努力

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

文章目录

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

前言

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

一、 watch 监视的 5 种情况

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

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

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

<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(深度监视) 。

  • 注意:

  • 若修改的是对象内部的属性,newValue 和 oldValue 是完全相同的(因为它们指向内存中的同一个对象) 。
  • 若修改了整个对象(替换了地址),newValue 为新对象,oldValue 为旧对象 。

<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 是无效的 。同样,回调中的 newValue 和 oldValue 指向同一个对象,二者相等 。

代码段

<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 函数形式(即一个返回值的箭头函数) 。

  • 若该属性值是基本类型:必须写成函数形式 。
  • 若该属性值是对象类型:可以直接写,也可以写成函数,更推荐写成函数 。若写成函数式且关注该属性内部的变化,需要手动开启 { deep: true } 。
  • <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:监视上述多个数据源的【组合】

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

    <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 的对比

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

  • 不同点:

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

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

  • 2.3 代码示例

    <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 。

    <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 宏显式地把允许外部访问的内容暴露出去 。

    <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>

    <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 父组件:通过属性绑定传递数据

    <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 引入 。它支持三种常见的接收写法 :

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

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

    <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(比如 ref、reactive、computed、生命周期钩子等)进行了高度分块封装 。

    6.2 规范与设计

  • 模块文件通常存放在项目的 src/hooks/ 目录下 。
  • 文件名和函数名规范以 use 开头(例如:useSum.ts、useMouse.ts)。
  • 在 Hook 内部定义独立的响应式数据和业务方法,并最终将外部需要用到的数据、方法通过 return 暴露出来。
  • 6.3 简单实战:封装一个管理累加功能的 Hook

    • 步骤 1:抽取封装逻辑 (src/hooks/useSum.ts)

    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)

    <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 的精髓在于让逻辑回归功能本身。

    赞(0)
    未经允许不得转载:171主机测评 » Vue3组合式API中watch传值生命周期与自定义Hook实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址