Vue3的组合式API彻底改变了我们编写Vue组件的方式。作为一名长期使用Vue2的开发者,我最初对这种新范式也感到不适应,但经过多个项目的实战后,发现它确实能带来更好的代码组织和复用性。
setup是组合式API的核心入口,它的执行时机比beforeCreate还要早。这意味着在setup中:
javascript复制<script>
export default {
setup() {
const message = 'Hello Vue3'
const logMessage = () => {
console.log(message)
}
return {
message,
logMessage
}
}
}
</script>
实际开发中,我强烈推荐使用
<script setup>语法糖。它不仅消除了return的样板代码,还能获得更好的类型推断(对TypeScript支持更友好)。
reactive最适合处理对象类型的响应式数据:
javascript复制import { reactive } from 'vue'
const state = reactive({
user: {
name: '张三',
age: 25
},
permissions: ['read', 'write']
})
ref则更通用,能处理任何类型的数据:
javascript复制import { ref } from 'vue'
const count = ref(0)
const user = ref({ name: '李四' })
经验法则:当确定数据会是对象形式时用reactive,其他情况用ref。在组合式函数中返回响应式数据时,统一使用ref可以避免解构丢失响应性的问题。
除了基本用法,computed还可以配置getter和setter:
javascript复制const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
注意:避免在计算属性中执行异步操作或DOM操作,这违反了计算属性的设计初衷。我曾在一个项目中犯过这个错误,导致难以追踪的bug。
对于复杂对象,我们需要深度监听:
javascript复制const state = reactive({
user: {
name: '王五',
address: {
city: '北京'
}
}
})
watch(
() => state.user,
(newVal) => {
console.log('用户信息变化:', newVal)
},
{ deep: true }
)
更高效的实践是精确监听特定属性:
javascript复制watch(
() => state.user.address.city,
(newCity) => {
console.log('城市变更:', newCity)
}
)
父传子使用defineProps:
javascript复制// 子组件
<script setup>
const props = defineProps({
title: {
type: String,
required: true
},
count: Number
})
</script>
子传父使用defineEmits:
javascript复制// 子组件
<script setup>
const emit = defineEmits(['updateCount'])
const handleClick = () => {
emit('updateCount', 10)
}
</script>
provide/inject比props更适合深层嵌套组件:
javascript复制// 祖先组件
<script setup>
import { provide, ref } from 'vue'
const darkMode = ref(false)
provide('theme', {
darkMode,
toggleTheme: () => {
darkMode.value = !darkMode.value
}
})
</script>
// 后代组件
<script setup>
import { inject } from 'vue'
const { darkMode, toggleTheme } = inject('theme')
</script>
重要提示:为避免命名冲突,建议为provide的key使用Symbol:
javascript复制const themeKey = Symbol()
provide(themeKey, { ... })
javascript复制<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null)
onMounted(() => {
inputRef.value.focus()
})
</script>
<template>
<input ref="inputRef" type="text">
</template>
javascript复制// 子组件
<script setup>
const count = ref(0)
const increment = () => {
count.value++
}
defineExpose({
increment
})
</script>
// 父组件
<script setup>
import { ref } from 'vue'
import ChildComp from './ChildComp.vue'
const child = ref(null)
const callChildMethod = () => {
child.value.increment()
}
</script>
<template>
<ChildComp ref="child" />
<button @click="callChildMethod">调用子组件方法</button>
</template>
javascript复制<script setup>
defineOptions({
name: 'MyComponent',
inheritAttrs: false
})
</script>
传统方式:
javascript复制// 子组件
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
</template>
使用defineModel后:
javascript复制// 子组件
<script setup>
const modelValue = defineModel()
</script>
<template>
<input v-model="modelValue">
</template>
性能提示:对于表单密集的应用,defineModel能减少约30%的样板代码,同时保持完全相同的性能特征。
组合式API的生命周期钩子:
javascript复制<script setup>
import { onMounted, onUpdated, onUnmounted } from 'vue'
onMounted(() => {
console.log('组件挂载')
})
onUpdated(() => {
console.log('组件更新')
})
onUnmounted(() => {
console.log('组件卸载')
})
</script>
调试技巧:可以在同一个组件中多次调用同一个钩子,它们会按注册顺序执行。这在大型组件中拆分不同功能的初始化逻辑时特别有用。
javascript复制const state = reactive({ count: 0 })
let { count } = state // 失去响应性!
javascript复制const user = ref({ name: '张三' })
user = { name: '李四' } // 错误!
user.value = { name: '李四' } // 正确
javascript复制const el = ref(null)
// 错误:立即访问
console.log(el.value) // null
// 正确:在生命周期或事件中访问
onMounted(() => {
console.log(el.value) // 正确获取DOM
})
javascript复制const stop = watchEffect(() => {
// 副作用代码
})
// 组件卸载时
onUnmounted(stop)
经过多个Vue3项目的实践,我发现组合式API虽然学习曲线略陡,但一旦掌握,能显著提高代码的可维护性和复用性。特别是与TypeScript配合使用时,类型推断能帮助捕获许多潜在错误。