vueuse-axios-conventions

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

VueUse Axios Conventions (EMID Project)

VueUse Axios 约定(EMID项目)

This skill documents the standardized patterns for data fetching and state management using
useAxios
in the EMID frontend.
本规范记录了EMID前端项目中使用
useAxios
实现数据请求和状态管理的标准模式。

1. Centralized Error Handling

1. 集中式错误处理

NEVER use manual
try-catch
blocks or
ElMessage.error
for API errors in components. The
axiosInstance
in
src/utils/http.ts
includes a global interceptor that:
  • Automatically formats error messages.
  • Displays them via
    ElMessage.error
    .
  • Logs details to the console for debugging.
Incorrect:
typescript
try {
  await execute();
} catch (error) {
  ElMessage.error("Failed to fetch");
}
Correct:
typescript
// Error is handled automatically by http.ts
await execute().catch(() => undefined);
切勿在组件中手动编写
try-catch
块或者
ElMessage.error
来处理API错误。
src/utils/http.ts
中的
axiosInstance
已经内置了全局拦截器,可实现:
  • 自动格式化错误信息
  • 通过
    ElMessage.error
    自动弹窗提示
  • 向控制台输出详细日志便于调试
错误示例:
typescript
try {
  await execute();
} catch (error) {
  ElMessage.error("Failed to fetch");
}
正确示例:
typescript
// 错误会被 http.ts 自动处理
await execute().catch(() => undefined);

2. Naming Conventions

2. 命名规范

Use semantic, verb+noun names. NEVER use the
execute
prefix (e.g.,
executeDelete
,
executeFetch
).
使用语义化的动词+名词结构命名,切勿使用
execute
前缀(例如
executeDelete
executeFetch
这类命名都是不允许的)。

Rule: Only rename when there are multiple useAxios calls in the same component

规则:仅当同一组件内有多个useAxios调用时才需要重命名

PropertyOnly 1 in componentMultiple in component
data
Keep
data
Rename to content meaning:
detail
,
materials
,
drafts
execute
Keep
execute
Rename to semantic verb:
fetchDetail
,
deleteDraft
,
uploadFile
isLoading
Keep
isLoading
Rename to scenario:
isSubmitting
,
isDownloading
,
draftsLoading
Incorrect:
typescript
const { execute: executeFetch } = useAxios(...)
const { execute: executeDeleteDraft } = useAxios(...)
const { data: detailsData } = useAxios(...)
const { data: dataStatsRes } = useAxios(...)
Correct:
typescript
// Single useAxios — no rename needed
const { data, execute, isLoading } = useAxios(...)

// Multiple useAxios — use semantic names
const { data: detail, execute: fetchDetail } = useAxios(...)
const { execute: deleteDraft } = useAxios(...)
const { data: materials, execute: fetchMaterials, isLoading: materialsLoading } = useAxios(...)
属性名组件内仅一个调用时组件内多个调用时
data
保留原名
data
重命名为对应内容含义:
detail
(详情)、
materials
(物料)、
drafts
(草稿)
execute
保留原名
execute
重命名为语义化动词:
fetchDetail
(获取详情)、
deleteDraft
(删除草稿)、
uploadFile
(上传文件)
isLoading
保留原名
isLoading
重命名为对应场景:
isSubmitting
(提交中)、
isDownloading
(下载中)、
draftsLoading
(草稿加载中)
错误示例:
typescript
const { execute: executeFetch } = useAxios(...)
const { execute: executeDeleteDraft } = useAxios(...)
const { data: detailsData } = useAxios(...)
const { data: dataStatsRes } = useAxios(...)
正确示例:
typescript
// 单个useAxios调用 — 无需重命名
const { data, execute, isLoading } = useAxios(...)

// 多个useAxios调用 — 使用语义化名称
const { data: detail, execute: fetchDetail } = useAxios(...)
const { execute: deleteDraft } = useAxios(...)
const { data: materials, execute: fetchMaterials, isLoading: materialsLoading } = useAxios(...)

3. Simplified Signature & Defaults

3. 简化的签名与默认值

useAxios
defaults to
immediate: true
when only a URL and instance are passed. But providing an options object replaces the defaults, making
immediate
become
undefined
(falsy).
  • Always declare
    immediate: true
    explicitly
    when passing an options object and wanting auto-execution.
  • Omit
    immediate: false
    when the intent is obvious from context (e.g., mutations with empty URL).
typescript
// Auto-fetch on mount — must declare immediate explicitly since options object is provided
const { data, isLoading } = useAxios<T>("/url", axiosInstance, {
  immediate: true,
  initialData: { results: [], count: 0 },
});

// Auto-fetch — no options object, immediate defaults to true
const { data } = useAxios<T>("/url", axiosInstance);

// Manual execution — immediate defaults to false when options aren't provided
// because the empty URL signals manual use
const { execute: submitMaterial, isLoading: isSubmitting } = useAxios<T>(
  "",
  axiosInstance,
);
const { execute: deleteDraft } = useAxios(
  "",
  { method: "DELETE" },
  axiosInstance,
);
当仅传入URL和实例时,
useAxios
默认开启
immediate: true
如果传入了配置对象,默认值会被覆盖,
immediate
会变为
undefined
(假值)。
  • 当你传入配置对象且希望组件挂载时自动执行请求,务必显式声明
    immediate: true
  • 当上下文能明显看出需要手动执行时,可以省略
    immediate: false
    (例如URL为空的变更类请求)
typescript
// 挂载时自动请求 — 因为传入了配置对象,必须显式声明immediate
const { data, isLoading } = useAxios<T>("/url", axiosInstance, {
  immediate: true,
  initialData: { results: [], count: 0 },
});

// 挂载时自动请求 — 没有配置对象,immediate默认为true
const { data } = useAxios<T>("/url", axiosInstance);

// 手动执行 — 未提供配置时immediate默认为false,空URL也明确表示需要手动调用
const { execute: submitMaterial, isLoading: isSubmitting } = useAxios<T>(
  "",
  axiosInstance,
);
const { execute: deleteDraft } = useAxios(
  "",
  { method: "DELETE" },
  axiosInstance,
);

4. Leverage Internal Loading States

4. 利用内置加载状态

Always use the
isLoading
property returned by
useAxios
instead of defining a separate
loading
ref.
typescript
const { isLoading: isSubmitting } = useAxios("/url", axiosInstance);

// In template:
// <el-button :loading="isSubmitting">Submit</el-button>
始终使用
useAxios
返回的
isLoading
属性,不要单独定义额外的
loading
ref。
typescript
const { isLoading: isSubmitting } = useAxios("/url", axiosInstance);

// 在模板中使用:
// <el-button :loading="isSubmitting">Submit</el-button>

5. Use initialData to Simplify Templates

5. 使用initialData简化模板

When fetching objects or lists, provide
initialData
to pre-populate the
data
ref. This eliminates repeated optional chaining (
?.
) in Vue templates.
typescript
const { data } = useAxios<PaginatedResponse>("/url", axiosInstance, {
  immediate: true,
  initialData: { results: [], count: 0 },
});

// In template — no need for data?.results
// <el-table :data="data.results">
// <el-pagination :total="data.count">
请求对象或列表时,传入
initialData
预先填充
data
ref,这样可以避免Vue模板中反复写可选链操作符 (
?.
)。
typescript
const { data } = useAxios<PaginatedResponse>("/url", axiosInstance, {
  immediate: true,
  initialData: { results: [], count: 0 },
});

// 在模板中使用 — 不需要写 data?.results
// <el-table :data="data.results">
// <el-pagination :total="data.count">

6. Callbacks: onSuccess and onError

6. 回调:onSuccess和onError

Use
onSuccess
for post-request logic (closing modals, toasts, state updates). Use
onError
for navigation on failure (e.g., redirect to home on 404).
typescript
// onSuccess — handle side effects after successful request
const { execute: updateFiles } = useAxios<FileUpload[]>(
  "/upload/file/update/",
  { method: "POST" },
  axiosInstance,
  {
    onSuccess: (files) => {
      ElMessage.success("Upload success!");
      categoryRef.value = [...categoryRef.value, ...files];
      isDialogVisible.value = false;
    },
  },
);

// onError — redirect on failure
const { data: detail, execute: fetchDetail } = useAxios<MaterialDetail>(
  "",
  axiosInstance,
  {
    onSuccess: ({ expSerial }) => {
      /* process data */
    },
    onError: () => router.push("/"),
  },
);
使用
onSuccess
处理请求成功后的逻辑(关闭弹窗、提示消息、更新状态)。使用
onError
处理请求失败后的跳转逻辑(例如404时跳转到首页)。
typescript
// onSuccess — 处理请求成功后的副作用
const { execute: updateFiles } = useAxios<FileUpload[]>(
  "/upload/file/update/",
  { method: "POST" },
  axiosInstance,
  {
    onSuccess: (files) => {
      ElMessage.success("Upload success!");
      categoryRef.value = [...categoryRef.value, ...files];
      isDialogVisible.value = false;
    },
  },
);

// onError — 请求失败时跳转
const { data: detail, execute: fetchDetail } = useAxios<MaterialDetail>(
  "",
  axiosInstance,
  {
    onSuccess: ({ expSerial }) => {
      /* 处理数据 */
    },
    onError: () => router.push("/"),
  },
);

7. Using execute() Return Value

7. 使用execute()返回值

execute()
returns
StrictUseAxiosReturn
, which gives you access to
data
,
error
, etc. Use this instead of a separate
data
ref
when you need an inline result.
typescript
// ✅ Use return value — avoids needing a separate data ref
const { execute: uploadFile } = useAxios<FileUpload>(
  "/upload/file/",
  { method: "POST" },
  axiosInstance,
  {
    abortPrevious: false,
  },
);

const handleFileUpload = async ({ file }: UploadRequestOptions) => {
  const formData = new FormData();
  formData.append("file", file);
  const { data } = await uploadFile({ data: formData });
  return data.value!;
};

// ✅ Use return value in submit flow
const response = await submitMaterial(url, { method, data }).catch(
  () => undefined,
);
if (!response) return;
router.push(`/details/${response.data.value.id}`);
execute()
返回
StrictUseAxiosReturn
类型,你可以直接获取
data
error
等属性。当你需要内联获取请求结果时,优先使用这个返回值而不是额外定义一个data ref
typescript
// ✅ 使用返回值 — 避免额外定义data ref
const { execute: uploadFile } = useAxios<FileUpload>(
  "/upload/file/",
  { method: "POST" },
  axiosInstance,
  {
    abortPrevious: false,
  },
);

const handleFileUpload = async ({ file }: UploadRequestOptions) => {
  const formData = new FormData();
  formData.append("file", file);
  const { data } = await uploadFile({ data: formData });
  return data.value!;
};

// ✅ 在提交流程中使用返回值
const response = await submitMaterial(url, { method, data }).catch(
  () => undefined,
);
if (!response) return;
router.push(`/details/${response.data.value.id}`);

8. Static vs. Dynamic Configuration

8. 静态与动态配置

A. Define behavior at initialization (Static)

A. 初始化时定义行为(静态)

  • Hooks & Callbacks:
    onSuccess
    ,
    onError
  • Behavior:
    immediate
    ,
    initialData
    ,
    abortPrevious
  • Base Request Config: Fixed
    headers
    ,
    method
    , or the base
    url
  • 钩子与回调
    onSuccess
    onError
  • 行为配置
    immediate
    initialData
    abortPrevious
  • 基础请求配置:固定的
    headers
    method
    或者基础
    url

B. Pass only variables to execute (Dynamic)

B. 仅向execute传递变量(动态)

  • Request Body:
    data
    (for POST/PUT)
  • Query Params:
    params
  • URL Overrides: Dynamic URL or one-off configs
typescript
// 1. Static config at initialization
const { execute: searchImages } = useAxios<Response>(
  "/services/",
  { method: "POST" },
  axiosInstance,
  {
    onSuccess: ({ images }) => (serialImages.value = images),
  },
);

// 2. Dynamic data at execution time
const onSearch = () =>
  searchImages({
    data: { filters: JSON.stringify(filterItems.value) },
  }).catch(() => undefined);
  • 请求体
    data
    (POST/PUT请求使用)
  • 查询参数
    params
  • URL覆盖:动态URL或者一次性的特殊配置
typescript
// 1. 初始化时定义静态配置
const { execute: searchImages } = useAxios<Response>(
  "/services/",
  { method: "POST" },
  axiosInstance,
  {
    onSuccess: ({ images }) => (serialImages.value = images),
  },
);

// 2. 执行时传入动态数据
const onSearch = () =>
  searchImages({
    data: { filters: JSON.stringify(filterItems.value) },
  }).catch(() => undefined);

9. Paginated List Pattern

9. 分页列表模式

For components with paginated tables, use
params
with
reactive
+
watch
to auto-refetch:
typescript
const params = reactive({ page: 1, pageSize: 10 });

const {
  data,
  execute: fetchData,
  isLoading,
} = useAxios<PaginatedResponse>("/items/", { params }, axiosInstance, {
  immediate: true,
  initialData: { results: [], count: 0 },
});

// Auto-refetch when params change
watch(params, () => fetchData().catch(() => undefined));
对于带分页表格的组件,使用
reactive
定义
params
+
watch
实现参数变化时自动重新请求:
typescript
const params = reactive({ page: 1, pageSize: 10 });

const {
  data,
  execute: fetchData,
  isLoading,
} = useAxios<PaginatedResponse>("/items/", { params }, axiosInstance, {
  immediate: true,
  initialData: { results: [], count: 0 },
});

// 参数变化时自动重新请求
watch(params, () => fetchData().catch(() => undefined));

10. Mutation with Refresh Pattern

10. 带刷新的变更操作模式

For delete/update operations that need to refresh a list afterward:
typescript
const { execute: deleteItem } = useAxios(
  "",
  { method: "DELETE" },
  axiosInstance,
  {
    onSuccess: () => {
      ElMessage.success("Deleted");
      if (data.value.results.length === 1 && params.page > 1) params.page -= 1;
      else fetchData().catch(() => undefined);
    },
  },
);

const handleDelete = async (id: number) => {
  try {
    await ElMessageBox.confirm("确认删除?", "提示", { type: "warning" });
    await deleteItem(`/items/${id}/`);
  } catch {
    // Only swallow 'cancel'
  }
};
对于删除/更新操作,操作完成后需要刷新列表的场景可以参考以下写法:
typescript
const { execute: deleteItem } = useAxios(
  "",
  { method: "DELETE" },
  axiosInstance,
  {
    onSuccess: () => {
      ElMessage.success("Deleted");
      if (data.value.results.length === 1 && params.page > 1) params.page -= 1;
      else fetchData().catch(() => undefined);
    },
  },
);

const handleDelete = async (id: number) => {
  try {
    await ElMessageBox.confirm("确认删除?", "提示", { type: "warning" });
    await deleteItem(`/items/${id}/`);
  } catch {
    // 仅吞掉用户主动取消的异常
  }
};