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 里。

Vue Composition API 用户列表流程图

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. 常见错误

错误后果修正
请求不写 finallyloading 可能一直 true在 finally 里关闭
reactive 随意解构响应式丢失用整体对象或 toRefs
watch 太深太频繁请求过多加 debounce 或明确监听字段
表单和列表混在页面里页面难维护抽 composable 和表单组件

9. 下一步怎么学

本篇把 Vue 页面从静态展示推进到真实交互:

能力对应任务
reactive搜索条件
ref列表、loading、error
onMounted初次加载
computed统计和派生数据
watch条件变化后请求
composable抽业务逻辑

下一篇建议:大白话讲解——Vue 进阶篇:Router Pinia 权限 工程化.md