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

相关推荐
萌萌哒草头将军15 分钟前
🚀🚀🚀 深入探索 Node.js v22.18.0 新特性;默认支持运行 ts 文件了!
前端·typescript·node.js
安心不心安30 分钟前
React ahooks——副作用类hooks之useThrottleFn
前端·javascript·react.js
秋田君40 分钟前
Vue3 + WebSocket网页接入弹窗客服功能的完整实现
前端·javascript·websocket·网络协议·学习
浪里行舟1 小时前
一网打尽 Promise 组合技:race vs any, all vs allSettled,再也不迷糊!
前端·javascript·vue.js
Antonio9151 小时前
【网络编程】WebSocket 实现简易Web多人聊天室
前端·网络·c++·websocket
德育处主任Pro2 小时前
p5.js 用 beginGeometry () 和 endGeometry () 打造自定义 3D 模型
开发语言·javascript·3d
今禾3 小时前
Zustand状态管理(上):现代React应用的轻量级状态解决方案
前端·react.js·前端框架
用户2519162427113 小时前
Canvas之图形变换
前端·javascript·canvas
今禾3 小时前
Zustand状态管理(下):从基础到高级应用
前端·react.js·前端框架