第 13 章:Composition API 实战
学习目标
- 对比 Options API 与 Composition API
- 用
ref/computed/watch组织业务 - 抽取逻辑到组合式函数
- 选出当前场景的最优写法
一、Options API 写法回顾
vue
<script lang="ts">
export default {
data() {
return { count: 0, keyword: '' }
},
computed: {
filtered() { return this.list.filter(i => i.name.includes(this.keyword)) },
},
watch: {
keyword(v) { this.fetch(v) },
},
methods: {
inc() { this.count++ },
async fetch(k: string) { /* ... */ },
},
mounted() { this.fetch('') },
}
</script>二、Composition API 等价改写
vue
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
const count = ref(0)
const keyword = ref('')
const list = ref<Item[]>([])
const filtered = computed(() =>
list.value.filter(i => i.name.includes(keyword.value))
)
watch(keyword, (v) => fetchList(v))
const inc = () => count.value++
async function fetchList(k: string) {
const res = await api.list({ keyword: k })
list.value = res.data
}
onMounted(() => fetchList(''))
</script>代码按业务逻辑组织,不再被 data/computed/watch 隔开。
三、优缺点对比
| 维度 | Options | Composition |
|---|---|---|
| 学习曲线 | 友好 | 需熟悉 hooks |
| 复用 | mixin(有命名冲突) | composables |
| 大组件 | 难维护 | 抽取清晰 |
| TS 推断 | 一般 | 强 |
四、实战:搜索 + 分页
ts
// src/composables/useSearch.ts
import { ref, computed, watch } from 'vue'
export function useSearch<T>(
fetcher: (params: { keyword: string; page: number }) => Promise<{ items: T[]; total: number }>,
) {
const keyword = ref('')
const page = ref(1)
const pageSize = 20
const list = ref<T[]>([])
const total = ref(0)
const loading = ref(false)
const totalPages = computed(() => Math.ceil(total.value / pageSize))
async function load() {
loading.value = true
try {
const res = await fetcher({ keyword: keyword.value, page: page.value })
list.value = res.items
total.value = res.total
} finally {
loading.value = false
}
}
watch(keyword, () => { page.value = 1; load() })
watch(page, load)
return { keyword, page, list, total, totalPages, loading, load }
}vue
<script setup lang="ts">
import { useSearch } from '@/composables/useSearch'
import { fetchUsers } from '@/api/user'
const { keyword, page, list, totalPages, loading } = useSearch(fetchUsers)
</script>⚠️ 坑 1:组合式函数必须以
use开头命名,否则 lint 报错。 ⚠️ 坑 2:组合式函数里不要直接调用生命周期钩子以外的副作用,惰性用watch包裹。
五、provide / inject 配合组合式
ts
// src/composables/useTheme.ts
import { provide, inject, ref, type InjectionKey } from 'vue'
const ThemeKey: InjectionKey<Ref<string>> = Symbol('theme')
export function provideTheme() {
const theme = ref('light')
provide(ThemeKey, theme)
return theme
}
export function useTheme() {
return inject(ThemeKey, ref('light'))
}vue
<!-- App.vue -->
<script setup lang="ts">
const theme = provideTheme()
</script>
<!-- 后代组件 -->
<script setup lang="ts">
const theme = useTheme()
</script>⚠️ 坑 3:
InjectionKey不写的话,inject 拿到的是unknown类型。
六、本章小结
| 场景 | 写法 |
|---|---|
| 简单组件 | Options 或 Setup 皆可 |
| 复杂业务 | Setup + 组合式函数 |
| 跨组件上下文 | provide / inject |
| 类型安全 | InjectionKey<T> |
动手练习
- 重构:把 Options API 写的搜索页改成 Composition API
- 抽取:把分页逻辑抽成
usePagination - 主题切换:用 composable 包装 theme,所有组件可读
下一章:第 14 章:组合式函数 →