引言
在 Vue3 中,Composition API 彻底改变了我们编写组件的方式。作为其核心,setup
函数和 emit
方法构成了组件逻辑组织和通信的基础。本文将带你深入理解这些特性的工作原理、设计哲学和最佳实践。
一、setup 函数:组件逻辑的新家园
1.1 基本概念
setup
是 Composition API 的入口点,它在组件创建之前执行:
javascript
export default {
setup(props, context) {
// 在这里定义响应式状态和方法
return {
// 暴露给模板的内容
}
}
}
1.2 执行时机与生命周期
- 调用时机 :在
beforeCreate
之前执行 - 与选项式API对比:
阶段 | 选项式API | Composition API |
---|---|---|
初始化 | data() | setup() |
方法定义 | methods | setup() |
计算属性 | computed | setup() + computed() |
生命周期 | 特定钩子 | onMounted() 等 |
1.3 底层实现剖析
Vue3 处理 setup 的核心流程:
typescript
// 简化后的源码逻辑
function setupComponent(instance) {
const setupResult = setup(instance.props, setupContext)
if (isFunction(setupResult)) {
instance.render = setupResult
} else if (isObject(setupResult)) {
instance.setupState = proxyRefs(setupResult)
}
}
二、emit:组件通信的桥梁
2.1 基本用法
javascript
// 子组件
setup(props, { emit }) {
const onClick = () => {
emit('submit', { data: 'test' })
}
return { onClick }
}
2.2 类型安全的 emit
typescript
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
2.3 与 v-model 的集成
javascript
emit('update:modelValue', newValue)
三、setup 返回值的神秘旅程
3.1 返回值去向
- 模板渲染上下文:通过代理使模板可访问
- 渲染函数作用域:用于 JSX/渲染函数
- 组件实例 :可通过
this
访问(与选项式API混用时)
3.2 响应式系统整合
flowchart LR
A[setup返回值] --> B[Proxy处理]
B --> C[依赖追踪]
C --> D[模板编译]
D --> E[渲染函数]
E --> F[虚拟DOM]
四、最佳实践与性能优化
4.1 代码组织建议
javascript
// 按功能组织代码
setup() {
// 用户相关逻辑
const { user, login } = useAuth()
// 数据获取逻辑
const { data, load } = useFetch('/api')
return { user, login, data, load }
}
4.2 性能优化技巧
-
避免不必要的响应式:
javascript// 不好的做法 setup() { const config = reactive({ static: true }) // 不需要响应式 } // 好的做法 setup() { const config = { static: true } // 普通对象 }
-
合理使用 shallowRef:
javascriptconst largeList = shallowRef([]) // 只跟踪引用变化
五、与 React Hooks 的对比
特性 | Vue setup | React Hooks |
---|---|---|
执行时机 | 组件创建前一次执行 | 每次渲染都可能执行 |
依赖数组 | 不需要 | 需要指定 |
条件限制 | 无限制 | 不能条件调用 |
状态保持机制 | 闭包 + 响应式系统 | 闭包 + 链表 |
六、实战:构建健壮的表格组件
结合 setup 和 emit 实现可排序表格:
vue
<script setup>
const props = defineProps({
data: Array,
columns: Array
})
const emit = defineEmits(['sort'])
const sortBy = ref('')
const sortDirection = ref('asc')
const handleSort = (column) => {
if (sortBy.value === column) {
sortDirection.value = sortDirection.value === 'asc' ? 'desc' : ''
if (!sortDirection.value) sortBy.value = ''
} else {
sortBy.value = column
sortDirection.value = 'asc'
}
emit('sort', {
column: sortBy.value,
direction: sortDirection.value
})
}
</script>
七、常见问题解答
Q: 为什么 setup 不能是异步函数?
A: 因为组件需要同步创建。可以使用 async/await
内部逻辑 + Suspense
组件:
javascript
setup() {
const data = ref(null)
async function load() {
data.value = await fetchData()
}
load()
return { data }
}
Q: setup 返回的函数会重新创建吗?
A: 不会。setup 只执行一次,返回的函数会保持闭包引用。
结语
Vue3 的 Composition API 通过 setup
和 emit
提供了更灵活、更强大的代码组织方式。理解其底层机制不仅能帮助我们写出更好的代码,还能在遇到问题时快速定位原因。随着 <script setup>
语法糖的普及,这种开发模式正在成为 Vue 开发的主流选择。