Vue3 怎么做 CRUD?从基础到实战带你理清思路
Vue3 里的 CRUD 到底指啥?和传统前端有啥不一样?
CRUD 是增(Create)、删(Delete)、改(Update)、查(Read)四个操作的缩写,是后端系统最基础的业务逻辑,在 Vue3 里做 CRUD,核心是用 Vue3 的特性(比如组合式 API、响应式数据)和后端接口交互,完成这四类操作。
和传统前端(jQuery 时代)相比,Vue3 优势很明显:
- 响应式自动更新 DOM:以前用 jQuery 改数据得手动操作 DOM,Vue3 用
v-for绑定响应式数组,数据变了 DOM 自动更新,开发效率翻倍; - 组合式 API 复用逻辑:把数据请求、加载状态这些重复逻辑封装成自定义 Hook,多个组件能直接复用;
- 生态工具更高效:搭配 Vite 打包(秒级启动开发服务)、Pinia 状态管理(轻量化替代 Vuex),流程更顺畅。
想做 Vue3 CRUD,得先搭好环境吧?基础项目咋初始化?
对的,先把项目架子搭起来!现在主流用 Vite + Vue3 初始化项目,步骤很简单:
-
创建项目:打开终端,执行
npm create vite@latest,输入项目名,选择Vue模板(JS 或 TS 看个人习惯); -
安装依赖:进入项目目录,执行
npm install装基础依赖,再执行npm run dev启动开发服务器(浏览器打开localhost:5173就能看到默认页面); -
装常用工具:
axios(发 HTTP 请求)、element-plus(UI 组件库,选其他库也成),执行npm install axios element-plus,然后在main.js里注册:import { createApp } from 'vue' import App from './App.vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' createApp(App).use(ElementPlus).mount('#app')
项目结构建议:src 下分 components(放通用组件)、views(放页面)、api(放接口请求函数),比如把和后端交互的请求封装到 api/request.js(axios 实例)和 api/modules/user.js(用户模块接口)里。
怎么实现“查(Read)”?列表渲染和数据请求咋结合?
“查”是最基础的操作,核心是调接口拿数据 + 渲染到页面,用组合式 API 写,逻辑会很清晰,以“用户列表”为例,在 src/views/UserList.vue 里这么写:
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
// 响应式数据:用户列表、加载状态
const userList = ref([])
const loading = ref(false)
// 获取用户列表的函数
const getUsers = async () => {
loading.value = true
try {
const res = await axios.get('/api/users') // 假设后端接口是这个
userList.value = res.data.data // 假设后端返回 { code: 200, data: [...] }
} catch (err) {
console.error('获取用户列表失败:', err)
// 这里可以用 Element Plus 的 Message 组件弹错误提示
} finally {
loading.value = false
}
}
// 组件挂载后自动请求数据
onMounted(() => {
getUsers()
})
</script>
<template>
<!-- 加载时显示骨架屏,数据加载完渲染表格 -->
<el-skeleton v-if="loading" :rows="5" />
<el-table v-else :data="userList">
<el-table-column prop="name" label="姓名" />
<el-table-column prop="age" label="年龄" />
<!-- 其他列 -->
</el-table>
</template>
如果是分页查询,还要加“页码、每页条数”参数:
- 给
el-table配el-pagination组件; - 请求时把
page、size传给后端; - 后端返回“总条数、当前页数据”,前端更新列表和分页组件。
“增(Create)”功能咋做?表单提交和数据新增要注意啥?
新增数据得做表单 + 接口提交,Vue3 里表单用 v-model 绑定数据(注意:Vue3 组件的 v-model 基于 modelValue 和 update:modelValue,和 Vue2 有区别),以“新增用户”为例,写个 UserCreate.vue 组件:
<script setup>
import { ref } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'
// 表单数据(响应式)
const userForm = ref({
name: '',
age: null,
gender: 'male'
})
// 提交表单
const onSubmit = async () => {
try {
await axios.post('/api/users', userForm.value)
ElMessage.success('新增成功!')
// 通知父组件刷新列表(比如触发事件)
$emit('created')
// 重置表单
userForm.value = { name: '', age: null, gender: 'male' }
} catch (err) {
ElMessage.error('新增失败,请检查数据')
console.error(err)
}
}
</script>
<template>
<el-form :model="userForm" label-width="80px">
<el-form-item label="姓名">
<el-input v-model="userForm.name" />
</el-form-item>
<el-form-item label="年龄">
<el-input-number v-model="userForm.age" />
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="userForm.gender">
<el-radio label="male">男</el-radio>
<el-radio label="female">女</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">提交</el-button>
</el-form-item>
</el-form>
</template>
注意点:
- 表单验证:用 Element Plus 的
rules配置验证规则(姓名不能为空”“年龄必须是数字”); - 刷新列表:新增成功后,父组件要重新调用“查”的接口,所以子组件用
$emit通知父组件; - 数据清洗:提交前检查数据格式(比如年龄转数字),避免后端报错。
“改(Update)”模块咋处理?编辑弹窗和数据回显有啥技巧?
编辑功能需要弹窗 + 数据回显 + 接口提交,思路是:点击“编辑”按钮时,把当前行数据传给弹窗,修改后调接口更新,写个 UserEdit.vue 组件示例:
<script setup>
import { ref, watch } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'
// 接收父组件传的“要编辑的用户数据”和“弹窗显隐状态”
const props = defineProps({
editUser: { type: Object, required: true },
visible: { type: Boolean, required: true }
})
const emit = defineEmits(['update:visible', 'updated'])
// 表单数据(深拷贝,避免直接修改父组件数据)
const form = ref({ ...props.editUser })
// 监听父组件传的 editUser 变化(比如切换编辑行时,更新表单)
watch(() => props.editUser, (newVal) => {
form.value = { ...newVal }
})
// 保存修改
const onSave = async () => {
try {
await axios.put(`/api/users/${form.value.id}`, form.value)
ElMessage.success('修改成功!')
emit('updated') // 通知父组件刷新列表
emit('update:visible', false) // 关闭弹窗
} catch (err) {
ElMessage.error('修改失败')
console.error(err)
}
}
</script>
<template>
<el-dialog v-model="visible" title="编辑用户">
<el-form :model="form" label-width="80px">
<el-form-item label="姓名">
<el-input v-model="form.name" />
</el-form-item>
<!-- 其他表单项 -->
</el-form>
<template #footer>
<el-button @click="emit('update:visible', false)">取消</el-button>
<el-button type="primary" @click="onSave">保存</el-button>
</template>
</el-dialog>
</template>
技巧:
- 数据回显:用
watch监听父组件传的editUser,确保切换编辑行时表单能正确显示数据; - 深拷贝数据:
editUser是复杂对象(比如嵌套结构),{ ...newVal }这种浅拷贝可能不够,得用lodash.cloneDeep或JSON.parse(JSON.stringify()); - 接口传 ID:修改接口一般需要带 ID,所以表单里要保留 ID 字段(可以隐藏)。
“删(Delete)”功能要注意啥?批量删除和单条删除咋实现?
删除分单条删除和批量删除,核心都是调后端 delete 接口,再更新前端列表。
单条删除
在用户列表的操作列加“删除”按钮,点击时传当前行 ID,调接口后从列表过滤掉该数据:
<el-table-column label="操作">
<template #default="scope">
<el-button type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
<script setup>
const handleDelete = async (id) => {
try {
await axios.delete(`/api/users/${id}`)
ElMessage.success('删除成功')
// 从列表中过滤掉已删除项
userList.value = userList.value.filter(item => item.id !== id)
} catch (err) {
ElMessage.error('删除失败')
console.error(err)
}
}
</script>
批量删除
需要勾选复选框,收集选中的 ID 数组,再调批量删除接口:
<el-table
:data="userList"
:row-key="row => row.id"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<!-- 其他列 -->
</el-table>
<el-button type="danger" @click="batchDelete" v-if="selectedRows.length">批量删除</el-button>
<script setup>
const selectedRows = ref([])
const handleSelectionChange = (rows) => {
selectedRows.value = rows
}
const batchDelete = async () => {
const ids = selectedRows.value.map(row => row.id)
try {
// 注意:delete 请求带请求体时,axios 要这么写(默认不传 data)
await axios.delete('/api/users/batch', { data: { ids } })
ElMessage.success('批量删除成功')
// 从列表中过滤掉已删除项
userList.value = userList.value.filter(item => !ids.includes(item.id))
selectedRows.value = [] // 清空选中
} catch (err) {
ElMessage.error('批量删除失败')
console.error(err)
}
}
</script>
注意点:
- 确认弹窗:删除前用
ElMessageBox.confirm加确认,防止误操作; - 接口协商:批量删除的接口设计多样(有的用
POST传ids,有的用DELETE带请求体),要和后端沟通好; - 列表更新:删除后及时更新前端列表,保证用户看到最新状态。
Vue3 CRUD 咋优化用户体验?加载态、错误提示这些细节咋做?
细节决定体验!这几个优化方向要重视:
加载态
- 列表请求:用
el-skeleton骨架屏(比如前面“查”的例子),或者el-loading指令; - 表单提交:给提交按钮加
loading属性,请求时置为true,结束后置为false:<el-button type="primary" :loading="loading" @click="onSubmit">提交</el-button>
错误提示
用 axios 拦截器 + Element Plus Message 统一处理错误,避免每个请求重复写提示:
// 在 api/request.js 里封装 axios
import axios from 'axios'
import { ElMessage } from 'element-plus'
const instance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 5000
})
// 响应拦截器:处理错误
instance.interceptors.response.use(
(response) => response,
(error) => {
if (error.response) {
// 后端返回错误(400、401、500)
ElMessage.error(`请求失败:${error.response.data.message || '未知错误'}`)
} else {
// 网络错误(比如断网)
ElMessage.error('网络异常,请稍后再试')
}
return Promise.reject(error)
}
)
export default instance
防抖节流
比如搜索框,用户输入后延迟请求,避免频繁调用接口,用 lodash.debounce 实现:
<script setup>
import { debounce } from 'lodash'
import request from '@/api/request'
const searchKeyword = ref('')
const getSearchList = debounce(async () => {
const res = await request.get('/api/users', {
params: { keyword: searchKeyword.value }
})
userList.value = res.data.data
}, 300) // 输入停止 300ms 后发请求
watch(searchKeyword, () => {
getSearchList()
})
</script>
前后端分离下,Vue3 咋和后端接口对接?跨域、token 这些问题咋解决?
对接后端核心靠 axios 封装 + 环境配置:
接口基地址 & 环境变量
在 Vite 里,用 .env.development(开发环境)和 .env.production(生产环境)配置基地址:
# .env.development VITE_API_BASE_URL = 'http://localhost:3000' # .env.production VITE_API_BASE_URL = 'https://your-domain.com'
然后在 axios 实例里用:
const instance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
// ...
})
Token 传递
用户登录后,Token 存在 localStorage 或 Pinia 里,用请求拦截器加到请求头:
instance.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
跨域问题
开发时,浏览器同源策略会拦截不同域名的请求,用 Vite Proxy 解决:
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
'/api': {
target: 'http://localhost:3000', // 后端服务地址
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '') // 把 /api 替换为空
}
}
}
})
生产环境用 Nginx 反向代理:
location /api {
proxy_pass http://backend:3000; # 后端服务地址
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
想做复杂场景的 CRUD,比如表格带树形结构、分页,Vue3 有啥现成组件库能用?
Vue3 生态里成熟的 UI 库很多,推荐这几个处理复杂表格:
Element Plus
- 树形表格:通过
tree-props配置父子关系({ children: 'children', hasChildren: 'hasChildren' }),后端返回数据要有children数组; - 分页:搭配
el-pagination组件,请求时传page、size,后端返回total和当前页数据。
示例(分页):
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


