Vue / 闯关模式
Vue 核心篇:Composition API 表单 请求 用户列表
用 Composition API 管搜索、表单、请求、loading、error,把用户列表变成真实业务页面。
一句话:Vue 核心能力就是用 Composition API 把响应式状态、表单、接口请求和业务逻辑组织清楚。
第 6 篇 / 共 12 篇。
本篇学完你会:用 Vue 3.5 + script setup + Composition API + TypeScript 写用户列表的搜索、请求、loading、error、新增编辑表单。
1. Composition API 解决什么问题
Composition API 让你按“业务能力”组织代码,而不是把 data、methods、computed 分散在不同位置。
用户管理页面可以拆成:
用户列表状态
搜索条件
加载用户
保存用户
删除用户
表单校验这些逻辑可以放在同一个 composable 里。

2. reactive 管搜索条件
<script setup lang="ts">
import { reactive } from 'vue';
type UserQuery = {
keyword: string;
status: 'all' | 'active' | 'disabled';
};
const query = reactive<UserQuery>({
keyword: '',
status: 'all'
});
</script>
<template>
<input v-model="query.keyword" placeholder="搜索用户名" />
<select v-model="query.status">
<option value="all">全部</option>
<option value="active">正常</option>
<option value="disabled">停用</option>
</select>
</template>3. ref 管 loading 和 error
const users = ref<User[]>([]);
const loading = ref(false);
const error = ref<string | null>(null);页面状态:
<p v-if="loading">加载中...</p>
<p v-else-if="error">{{ error }}</p>
<UserTable v-else :users="users" />大白话:真实页面至少要有三种状态,加载中、失败、成功。
4. 用户列表请求流程
async function loadUsers() {
loading.value = true;
error.value = null;
try {
const search = new URLSearchParams({
keyword: query.keyword,
status: query.status
});
const response = await fetch(`/api/users?${search.toString()}`);
if (!response.ok) {
throw new Error('用户列表加载失败');
}
users.value = (await response.json()) as User[];
} catch (currentError) {
error.value = currentError instanceof Error ? currentError.message : '未知错误';
} finally {
loading.value = false;
}
}组件挂载时加载:
onMounted(() => {
loadUsers();
});5. 新增和编辑表单
表单值:
type UserFormValue = {
name: string;
email: string;
role: 'admin' | 'member';
status: 'active' | 'disabled';
};
const form = reactive<UserFormValue>({
name: '',
email: '',
role: 'member',
status: 'active'
});保存:
const submitting = ref(false);
async function submitForm() {
submitting.value = true;
try {
await saveUser({ ...form });
await loadUsers();
} finally {
submitting.value = false;
}
}注意 { ...form }:提交时复制一份普通对象,避免把响应式对象直接丢给接口层。
6. computed 和 watch
computed 适合根据已有数据算结果:
const activeCount = computed(() => users.value.filter((user) => user.status === 'active').length);watch 适合观察变化后做事,比如搜索条件变化后重新请求:
watch(
() => ({ ...query }),
() => {
loadUsers();
}
);真实项目里搜索建议加 debounce,避免每输入一个字都请求一次。
7. composable 抽业务逻辑
把用户列表逻辑抽到 useUsers.ts:
export function useUsers() {
const query = reactive<UserQuery>({ keyword: '', status: 'all' });
const users = ref<User[]>([]);
const loading = ref(false);
const error = ref<string | null>(null);
async function loadUsers() {
// 请求逻辑
}
onMounted(loadUsers);
return {
query,
users,
loading,
error,
loadUsers
};
}页面使用:
const { query, users, loading, error, loadUsers } = useUsers();大白话:composable 就是 Vue 里的“可复用业务逻辑包”。
8. 常见错误
| 错误 | 后果 | 修正 |
|---|---|---|
| 请求不写 finally | loading 可能一直 true | 在 finally 里关闭 |
| reactive 随意解构 | 响应式丢失 | 用整体对象或 toRefs |
| watch 太深太频繁 | 请求过多 | 加 debounce 或明确监听字段 |
| 表单和列表混在页面里 | 页面难维护 | 抽 composable 和表单组件 |
9. 下一步怎么学
本篇把 Vue 页面从静态展示推进到真实交互:
| 能力 | 对应任务 |
|---|---|
| reactive | 搜索条件 |
| ref | 列表、loading、error |
| onMounted | 初次加载 |
| computed | 统计和派生数据 |
| watch | 条件变化后请求 |
| composable | 抽业务逻辑 |