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,体验其带来的开发效率提升和代码可维护性优势。

相关推荐
533_3 小时前
[vue3] h函数,阻止事件冒泡
javascript·vue.js·elementui
蒲公英源码3 小时前
php+vue知识付费系统前后端全套源码
vue.js·php
通往曙光的路上3 小时前
day22_用户授权 头像上传
javascript·vue.js·ecmascript
小阳生煎3 小时前
Vue实现全局设置一个刷新按钮 只刷新当面路由页面 不跳转操作功能
vue.js·vue
小光学长3 小时前
基于Vue的儿童手工创意店管理系统as8celp7(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
前端·数据库·vue.js
meichaoWen3 小时前
【Vue】Vue框架的基础知识强化
前端·javascript·vue.js
jingling5553 小时前
Flutter | 基础环境配置和创建flutter项目
前端·flutter
mapbar_front4 小时前
如何判断一个领导值不值得追随
前端
西西学代码4 小时前
Flutter---DragTarget(颜色拖拽选择器)
前端·javascript·flutter
小光学长4 小时前
基于Vue的地铁综合服务管理系统7949eg04(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
前端·数据库·vue.js