当前位置: 首页 > article >正文

深入探索Vue 3组合式API

深入探索Vue 3组合式API

  • 深入探索Vue 3组合式API
    • 一、组合式API诞生背景
      • 1.1 Options API的局限性
      • 1.2 设计目标
      • 二、核心概念解析
        • 2.1 setup() 函数:组合式API的基石
        • 2.2 响应式系统:重新定义数据驱动
        • 2.3 生命周期:全新的接入方式
        • 2.4 响应式原理探秘
        • 2.5 组合式API中的上下文处理
    • 三、核心API深度解析
      • 3.1 响应式工具
      • 3.2 计算属性
      • 3.3 副作用管理
    • 四、逻辑复用模式
      • 4.1 自定义组合函数
      • 4.2 异步状态管理
    • 五、最佳实践
      • 5.1 代码组织模式
      • 5.2 TypeScript集成
    • 六、与Options API对比
      • 6.1 逻辑组织对比

深入探索Vue 3组合式API

一、组合式API诞生背景

1.1 Options API的局限性

  • 代码组织碎片化
  • 逻辑复用困难(mixins缺陷)
  • 类型支持不足

1.2 设计目标

  • 更好的逻辑复用
  • 更灵活的组织方式
  • 更好的类型推导
  • 渐进式采用策略

二、核心概念解析

2.1 setup() 函数:组合式API的基石

(1)函数特性与执行时机

export default {
  setup(props, context) {
    // 在beforeCreate之前执行
    // 无法访问this
    // 返回对象将暴露给模板和其他选项
  }
}
  • 执行顺序:在组件实例创建之前同步执行,位于beforeCreatecreated生命周期之间
  • 参数解析
    • props:响应式的props对象,结构会失去响应性
    • context:包含attrs、slots、emit的非响应式对象
  • 返回值
    • 返回对象属性将合并到模板渲染上下文
    • 可返回渲染函数直接控制视图输出

(2)props处理规范

import { defineComponent } from 'vue'

interface Props {
  title: string
  count?: number
}

export default defineComponent({
  props: {
    title: String,
    count: {
      type: Number,
      default: 0
    }
  },
  setup(props: Props) {
    // 使用watch监听props变化
    watch(() => props.count, (newVal) => {
      console.log('count changed:', newVal)
    })
  }
})
  • 类型安全:配合TypeScript实现严格类型校验
  • 不可解构:直接解构props会导致响应性丢失,需使用toRefs
  • 默认值处理:当父组件未传值时自动应用默认值

(3)上下文对象解析

setup(props, { attrs, slots, emit, expose }) {
  // 访问非响应式属性:
  console.log(attrs.class)
  
  // 检查插槽内容:
  const hasFooter = slots.footer
  
  // 事件触发:
  emit('submit', payload)
  
  // 暴露公共属性:
  expose({ publicMethod })
}
  • attrs:包含未在props中声明的属性
  • slots:访问通过插槽分发的内容(v-slot语法)
  • emit:替代this.$emit的事件触发方式
  • expose:控制组件实例对外暴露的公共方法

(4)响应式状态管理

const state = reactive({
  user: {
    name: 'Alice',
    posts: []
  },
  loading: false
})

// 嵌套对象自动响应化
watchEffect(() => {
  console.log('User name changed:', state.user.name)
})
  • 深层响应性:reactive会递归转换对象属性
  • 数组处理:支持数组索引修改和length变更检测
  • 自动解包:在模板中访问ref无需.value,但JS环境中需要

2.2 响应式系统:重新定义数据驱动

(1)响应式核心原理

const targetMap = new WeakMap()

function track(target, key) {
  // 收集依赖
}

function trigger(target, key) {
  // 通知更新
}

const handler = {
  get(target, key, receiver) {
    track(target, key)
    return Reflect.get(...arguments)
  },
  set(target, key, value, receiver) {
    const result = Reflect.set(...arguments)
    trigger(target, key)
    return result
  }
}
  • Proxy代理:基于ES6 Proxy实现属性访问拦截
  • 依赖追踪:通过WeakMap建立目标对象->属性->依赖的映射关系
  • 批量更新:Vue的调度机制确保多次状态变更合并为单次更新

(2)ref的进阶用法

// DOM元素引用
const inputRef = ref<HTMLInputElement | null>(null)

// 组件挂载后访问
onMounted(() => {
  inputRef.value?.focus()
})

// 模板引用
<template>
  <input ref="inputRef">
</template>

// 复杂类型处理
const state = ref({
  user: {
    name: 'Bob'
  }
})

// 自动解包
state.value.user.name = 'Charlie'
  • 模板引用:替代this.$refs的声明方式
  • 类型标注:在TypeScript中明确指定引用类型
  • 对象嵌套:ref可以包裹复杂对象结构

(3)reactive的边界情况

// 响应式丢失场景
const state = reactive({ x: 0 })
let { x } = state // 值拷贝,失去响应性

// 正确解构方式
const { x } = toRefs(state)

// 数组处理特例
const list = reactive([1, 2, 3])
list = reactive([4,5,6]) // 错误!需要修改现有引用
list.push(4) // 正确方式

// 使用readonly保护
const protectedState = readonly(state)
protectedState.x++ // 控制台警告
  • 引用替换限制:必须保持对象引用不变
  • 数组变异方法:push/pop等标准方法可触发更新
  • 只读保护:防止意外修改共享状态

(4)响应式工具函数对比

方法作用典型场景
toRef为源响应式对象属性创建refprops解构时保持响应性
toRefs转换整个响应式对象为普通对象从组合函数返回响应式状态
isProxy检查是否为代理对象调试响应式系统
isReactive检查reactive创建的代理类型判断
isReadonly检查只读代理安全校验
markRaw标记对象永不转为响应式性能优化/集成第三方库
shallowRef创建浅层ref大型对象性能优化
triggerRef手动触发shallowRef更新强制刷新界面

2.3 生命周期:全新的接入方式

(1)完整生命周期映射表

Options APIComposition API触发时机
beforeCreate-被setup替代
created-被setup替代
beforeMountonBeforeMountDOM挂载开始前
mountedonMountedDOM挂载完成后
beforeUpdateonBeforeUpdate响应式数据变更导致更新前
updatedonUpdated虚拟DOM重新渲染后
beforeUnmountonBeforeUnmount组件卸载前(vue2的beforeDestroy别名)
unmountedonUnmounted组件卸载后(vue2的destroyed别名)
errorCapturedonErrorCaptured捕获后代组件错误时
renderTrackedonRenderTracked响应式依赖被追踪时(开发模式)
renderTriggeredonRenderTriggered响应式依赖触发更新时(开发模式)

(2)组合式生命周期示例

import { 
  onMounted,
  onUpdated,
  onUnmounted 
} from 'vue'

export default {
  setup() {
    // 同步调用保证正确注册
    onMounted(async () => {
      const data = await fetchData()
      // 异步操作不会阻塞生命周期
    })

    // 支持多次注册相同钩子
    onMounted(() => console.log('第一个mounted回调'))
    onMounted(() => console.log('第二个mounted回调'))

    // 清理副作用示例
    const timer = ref()
    onMounted(() => {
      timer.value = setInterval(() => {
        /* 定时任务 */
      }, 1000)
    })
    onUnmounted(() => {
      clearInterval(timer.value)
    })
  }
}

(3)生命周期使用原则

  1. 同步注册:必须在setup同步调用生命周期钩子
    // 错误示例
    setTimeout(() => {
      onMounted(() => {}) // 将不会执行
    }, 100)
    
  2. 执行顺序:按照注册顺序同步执行
  3. 异步操作:钩子函数本身可以包含异步代码,但不会延迟生命周期进度
  4. 组件树顺序:父组件onBeforeMount先于子组件onBeforeMount

(4)调试钩子实践

onRenderTracked((event) => {
  console.log('依赖追踪:', event)
})

onRenderTriggered((event) => {
  console.log('依赖触发更新:', event)
})
  • 开发模式专用:帮助分析组件渲染行为
  • 事件对象:包含target(响应式对象)、key(触发属性)、type(操作类型)等信息
  • 性能优化:识别不必要的渲染触发源

2.4 响应式原理探秘

(1)依赖收集流程

// 伪代码实现
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
      return Reflect.get(target, key)
    },
    set(target, key, value) {
      const result = Reflect.set(target, key, value)
      trigger(target, key)
      return result
    }
  })
}

// 副作用函数注册
let activeEffect
class ReactiveEffect {
  constructor(fn) {
    this.fn = fn
  }
  run() {
    activeEffect = this
    return this.fn()
  }
}

function watchEffect(fn) {
  const effect = new ReactiveEffect(fn)
  effect.run()
}

(2)响应式类型对比

refreactiveshallowRef
创建方式ref(value)reactive(object)shallowRef(value)
访问方式.value直接访问属性.value
嵌套响应自动展开递归响应非递归
类型支持基础类型/对象仅对象任意类型
模板自动解包支持不需要支持
适用场景独立基本值、模板引用复杂对象结构大型对象性能优化

(3)响应式转换规则

const raw = {}
const observed = reactive(raw)

console.log(observed === raw) // false
console.log(reactive(observed) === observed) // true

const refVal = ref(0)
console.log(ref(refVal) === refVal) // true
  • 代理唯一性:对同一原始对象多次调用reactive返回相同代理
  • ref保护机制:如果传入ref给ref构造函数,直接返回原ref
  • 原始对象保护:Vue不会代理Vue实例或代理对象

2.5 组合式API中的上下文处理

(1)跨层级访问示例

// 祖先组件
import { provide } from 'vue'

setup() {
  const theme = ref('dark')
  provide('theme', theme)
}

// 后代组件
import { inject } from 'vue'

setup() {
  const theme = inject('theme', 'light') // 默认值
  return { theme }
}

(2)模板引用转发

// 子组件
import { defineExpose } from 'vue'

setup() {
  const publicMethod = () => { /* ... */ }
  defineExpose({ publicMethod })
}

// 父组件
const childRef = ref()

onMounted(() => {
  childRef.value.publicMethod()
})

三、核心API深度解析

3.1 响应式工具

// 解构响应式对象
import { reactive, toRefs } from 'vue'

const state = reactive({
  x: 0,
  y: 0
})

const { x, y } = toRefs(state)

3.2 计算属性

const doubleCount = computed(() => count.value * 2)

3.3 副作用管理

// watchEffect自动追踪依赖
const stop = watchEffect(() => {
  console.log(`count变化: ${count.value}`)
})

// 精确控制的watch
watch(count, (newVal, oldVal) => {
  // 执行特定操作
})

四、逻辑复用模式

4.1 自定义组合函数

// useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function update(e) {
    x.value = e.pageX
    y.value = e.pageY
  }

  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  return { x, y }
}

// 组件使用
import { useMouse } from './useMouse'

export default {
  setup() {
    const { x, y } = useMouse()
    return { x, y }
  }
}

4.2 异步状态管理

// useFetch.js
import { ref } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)

  async function fetchData() {
    try {
      loading.value = true
      const response = await fetch(url)
      data.value = await response.json()
    } catch (err) {
      error.value = err
    } finally {
      loading.value = false
    }
  }

  return {
    data,
    error,
    loading,
    fetchData
  }
}

五、最佳实践

5.1 代码组织模式

export default {
  setup() {
    // 数据逻辑
    const { x, y } = useMouse()
    
    // 业务逻辑
    const { data, fetch } = useFetch('/api')
    
    // 其他逻辑
    const count = useCounter()

    return { x, y, data, fetch, count }
  }
}

5.2 TypeScript集成

interface User {
  id: number
  name: string
}

const users = ref<User[]>([])

六、与Options API对比

6.1 逻辑组织对比

// Options API
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() { this.count++ }
  },
  mounted() {
    console.log('挂载完成')
  }
}

// Composition API
export default {
  setup() {
    const count = ref(0)
    const increment = () => count.value++
    onMounted(() => console.log('挂载完成'))
    return { count, increment }
  }
}

http://www.kler.cn/a/532658.html

相关文章:

  • java项目验证码登录
  • 使用 Axios 获取用户数据并渲染——个人信息设置
  • 2025_2_4 C语言中关于free函数及悬空指针,链表的一级指针和二级指指针
  • 第 1 天:UE5 C++ 开发环境搭建,全流程指南
  • C#常用744单词
  • C++ 常用排序算法
  • 实例研究:设计一个文档编辑器(1)
  • 有用的sql链接
  • 调用DeepSeek API实现对本地数据库的AI管理
  • 手扶电梯缺陷检测数据集VOC+YOLO格式67张1类别
  • 【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.19 线性代数核武器:BLAS/LAPACK深度集成
  • 二叉树03(数据结构初阶)
  • Leetcode—922. 按奇偶排序数组 II【简单】
  • mybatis plus 持久化使用技巧及场景
  • VUE3 pinia的对象莫名其妙被赋值成其他对象
  • 基于Hadoop实现气象分析大屏可视化项目【源码+LW+PPT+解析】
  • 2025年时序数据库发展方向和前景分析
  • Java牙科诊所管理系统web医院病例挂号预约平台springboot/ssm代码编写
  • 刷题记录 动态规划-7: 63. 不同路径 II
  • 我主编的电子技术实验手册(24)——RL并联电路
  • Wide Deep 模型:记忆能力与泛化能力
  • NSSCTF Pwn [SWPUCTF 2022 新生赛]shellcode?题解
  • 网安学习xss和php反序列后的心得
  • minikube 的 Kubernetes 入门教程--Dify
  • [C++]C++中的常见异常和自定义异常
  • 半导体器件与物理篇6 MESFET