Vue3 Composition API

一、setup 配置详解

1.1 Vue2 中的 attrs 和 slots 回顾

在 Vue2 中,父组件通过标签属性向子组件传递数据时,通常需要在子组件中使用 props 接收。但即使不声明接收,这些属性也会存在于子组件实例的 $attrs 中。

示例对比:

javascript

复制代码
// Vue2 子组件
export default {
  props: ['receivedProp'], // 声明接收的属性
  created() {
    console.log(this.$attrs) // 未声明接收的属性会出现在这里
    console.log(this.$slots) // 父组件传递的插槽内容
  }
}

1.2 Vue3 setup 的两个关键点

执行时机
  • setupbeforeCreate 之前执行

  • 此时组件实例尚未创建,thisundefined

参数解析

setup 接收两个参数:

  1. props:包含组件外部传递且内部声明接收的属性

  2. context:上下文对象,包含:

    • attrs:未在 props 中声明的属性(相当于 Vue2 的 $attrs

    • slots:接收的插槽内容(相当于 Vue2 的 $slots

    • emit:触发自定义事件的函数(相当于 Vue2 的 $emit

完整示例:

javascript

复制代码
import { defineComponent } from 'vue'

export default defineComponent({
  props: ['title'],
  emits: ['change'], // 必须声明自定义事件
  setup(props, { attrs, slots, emit }) {
    console.log(props.title) // 访问声明的 prop
    console.log(attrs.customAttr) // 访问未声明的属性
    
    const handleClick = () => {
      emit('change', 'new value') // 触发事件
    }
    
    return {
      handleClick
    }
  }
})

二、响应式进阶:计算属性与监视

2.1 computed 函数

Vue3 的 computed 用法与 Vue2 类似,但更灵活:

javascript

复制代码
import { ref, computed } from 'vue'

setup() {
  const firstName = ref('张')
  const lastName = ref('三')
  
  // 只读计算属性
  const fullName = computed(() => `${firstName.value} ${lastName.value}`)
  
  // 可写计算属性
  const editableName = computed({
    get: () => `${firstName.value} ${lastName.value}`,
    set: (newValue) => {
      const [first, last] = newValue.split(' ')
      firstName.value = first
      lastName.value = last
    }
  })
  
  return { fullName, editableName }
}

2.2 watch 函数详解

Vue3 的 watch 功能强大但有一些注意事项:

javascript

复制代码
import { ref, reactive, watch } from 'vue'

setup() {
  const count = ref(0)
  const state = reactive({
    user: {
      name: 'Alice',
      age: 25
    }
  })
  
  // 情况1:监视 ref
  watch(count, (newVal, oldVal) => {
    console.log(`count变化: ${oldVal} -> ${newVal}`)
  })
  
  // 情况2:监视多个 ref
  watch([count, anotherRef], ([newCount, newAnother], [oldCount, oldAnother]) => {
    // 处理变化
  })
  
  // 情况3:监视 reactive 对象(注意 oldValue 问题)
  watch(
    () => state.user,
    (newUser, oldUser) => {
      // oldUser 可能与 newUser 相同!
    },
    { deep: true } // 虽然默认开启,但显式声明更清晰
  )
  
  // 情况4:监视 reactive 对象的特定属性
  watch(
    () => state.user.age,
    (newAge, oldAge) => {
      // 这里能正确获取 oldAge
    }
  )
}

2.3 watchEffect 智能监听

watchEffect 自动追踪回调中的响应式依赖:

javascript

复制代码
import { ref, watchEffect } from 'vue'

setup() {
  const count = ref(0)
  const message = ref('')
  
  watchEffect(() => {
    console.log(`count: ${count.value}, message: ${message.value}`)
    // 会自动追踪 count 和 message 的变化
  })
  
  // 实际应用:自动取消之前的请求
  const searchQuery = ref('')
  watchEffect(async (onCleanup) => {
    const query = searchQuery.value
    const controller = new AbortController()
    
    onCleanup(() => controller.abort()) // 清除副作用
    
    if (query) {
      const results = await fetchResults(query, {
        signal: controller.signal
      })
      // 处理结果
    }
  })
}

三、生命周期全解析

3.1 Vue2 与 Vue3 生命周期对比

Vue2 生命周期 Vue3 生命周期 (Options API) Vue3 Composition API
beforeCreate beforeCreate setup()
created created setup()
beforeMount beforeMount onBeforeMount
mounted mounted onMounted
beforeUpdate beforeUpdate onBeforeUpdate
updated updated onUpdated
beforeDestroy beforeUnmount onBeforeUnmount
destroyed unmounted onUnmounted

3.2 组合式 API 生命周期使用

javascript

复制代码
import { onMounted, onUnmounted } from 'vue'

setup() {
  // 鼠标位置跟踪示例
  const x = ref(0)
  const y = ref(0)
  
  const updatePosition = (e) => {
    x.value = e.pageX
    y.value = e.pageY
  }
  
  onMounted(() => {
    window.addEventListener('mousemove', updatePosition)
  })
  
  onUnmounted(() => {
    window.removeEventListener('mousemove', updatePosition)
  })
  
  return { x, y }
}

四、自定义 Hook 实践

自定义 Hook 是 Vue3 代码复用的利器:

4.1 鼠标位置跟踪 Hook

javascript

复制代码
// hooks/useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useMousePosition() {
  const x = ref(0)
  const y = ref(0)
  
  const updatePosition = (e) => {
    x.value = e.pageX
    y.value = e.pageY
  }
  
  onMounted(() => {
    window.addEventListener('mousemove', updatePosition)
  })
  
  onUnmounted(() => {
    window.removeEventListener('mousemove', updatePosition)
  })
  
  return { x, y }
}

// 在组件中使用
import { useMousePosition } from './hooks/useMousePosition'

setup() {
  const { x, y } = useMousePosition()
  return { x, y }
}

4.2 数据请求 Hook

javascript

复制代码
// hooks/useFetch.js
import { ref, isRef, unref, watchEffect } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)
  
  const fetchData = async () => {
    loading.value = true
    try {
      const response = await fetch(unref(url))
      data.value = await response.json()
      error.value = null
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }
  
  if (isRef(url)) {
    watchEffect(fetchData)
  } else {
    fetchData()
  }
  
  return { data, error, loading, retry: fetchData }
}

五、toRef 与 toRefs 深度解析

5.1 toRef 使用场景

javascript

复制代码
import { reactive, toRef } from 'vue'

setup() {
  const state = reactive({
    name: 'Alice',
    age: 25
  })
  
  // 保持响应式连接
  const nameRef = toRef(state, 'name')
  
  setTimeout(() => {
    state.name = 'Bob' // nameRef 也会更新
  }, 1000)
  
  return {
    nameRef // 可以在模板中直接使用
  }
}

5.2 toRefs 解构响应式对象

javascript

复制代码
import { reactive, toRefs } from 'vue'

setup() {
  const state = reactive({
    name: 'Alice',
    age: 25,
    address: {
      city: 'Beijing'
    }
  })
  
  // 解构后仍保持响应式
  const { name, age } = toRefs(state)
  
  // 嵌套对象需要单独处理
  const { city } = toRefs(state.address)
  
  return {
    name,
    age,
    city
  }
}

六、Vue3 新组件实战

6.1 Fragment 片段组件

Vue3 不再需要根标签:

vue

复制代码
<template>
  <header>...</header>
  <main>...</main>
  <footer>...</footer>
</template>

6.2 Teleport 传送门

将组件渲染到 DOM 中的其他位置:

vue

复制代码
<template>
  <button @click="showModal = true">打开弹窗</button>
  
  <Teleport to="body">
    <div v-if="showModal" class="modal">
      <div class="modal-content">
        <h2>标题</h2>
        <p>内容...</p>
        <button @click="showModal = false">关闭</button>
      </div>
    </div>
  </Teleport>
</template>

<script>
import { ref } from 'vue'
export default {
  setup() {
    const showModal = ref(false)
    return { showModal }
  }
}
</script>

6.3 Suspense 异步组件

优雅处理异步组件加载状态:

vue

复制代码
<template>
  <Suspense>
    <template #default>
      <AsyncComponent />
    </template>
    <template #fallback>
      <div class="loading-spinner">
        加载中...
      </div>
    </template>
  </Suspense>
</template>

<script>
import { defineAsyncComponent } from 'vue'

const AsyncComponent = defineAsyncComponent(() =>
  import('./components/AsyncComponent.vue')
)

export default {
  components: {
    AsyncComponent
  }
}
</script>

七、Composition API 优势总结

  1. 更好的代码组织:相关功能代码集中在一起

  2. 更好的逻辑复用:通过自定义 Hook 实现

  3. 更好的类型推断:对 TypeScript 支持更友好

  4. 更小的生产包:Tree-shaking 友好

对比示例:

javascript

复制代码
// Options API 方式
export default {
  data() {
    return {
      count: 0,
      searchQuery: ''
    }
  },
  computed: {
    filteredList() {
      // 基于 searchQuery 过滤列表
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    // 初始化代码
  }
}

// Composition API 方式
import { ref, computed, onMounted } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const searchQuery = ref('')
    
    const increment = () => {
      count.value++
    }
    
    const filteredList = computed(() => {
      // 过滤逻辑
    })
    
    onMounted(() => {
      // 初始化代码
    })
    
    return {
      count,
      searchQuery,
      increment,
      filteredList
    }
  }
}

结语

Vue3 的 Composition API 为开发者提供了更灵活、更强大的代码组织方式。通过本文的详细解析和丰富示例,相信你已经掌握了其核心概念和使用技巧。在实际开发中,建议从简单功能开始逐步尝试组合式 API,体验其带来的开发效率提升和代码可维护性优势。

相关推荐
于慨20 小时前
Lambda 表达式、方法引用(Method Reference)语法
java·前端·servlet
石小石Orz20 小时前
油猴脚本实现生产环境加载本地qiankun子应用
前端·架构
从前慢丶20 小时前
前端交互规范(Web 端)
前端
像我这样帅的人丶你还20 小时前
别再让JS耽误你进步了。
css·vue.js
@yanyu66620 小时前
07-引入element布局及spring boot完善后端
javascript·vue.js·spring boot
CHU72903520 小时前
便捷约玩,沉浸推理:线上剧本杀APP功能版块设计详解
前端·小程序
GISer_Jing21 小时前
Page-agent MCP结构
前端·人工智能
王霸天21 小时前
💥别再抄网上的Scale缩放代码了!50行源码教你写一个永不翻车的大屏适配
前端·vue.js·数据可视化
小领航21 小时前
用 Three.js + Vue 3 打造炫酷的 3D 行政地图可视化组件
前端·github
@大迁世界21 小时前
2026年React大洗牌:React Hooks 将迎来重大升级
前端·javascript·react.js·前端框架·ecmascript