vue3 组合函数中优雅的复用增删改查
在 vue3 新增的 setup
函数中我们可以更好抽离公用代码,通过组合函数在不同的地方使用,更好的复用代码
起因
在日常的业务中经常会遇到处理增删改查的情况,以前只是单纯封装api地址便于管理。但在每个业务逻辑中或多或少都会存在获取列表、获取详情、提交表单、修改表单、删除某项等需求,为什么不把这部分代码抽离出来复用呢?
思路
将增删改查拆分为 查询列表
查询详情
增/改表单
删除某项
,然后在在组合函数中处理相关所有逻辑。函数接收api地址等配置参数,返回页面中所需要的参数及函数。如下是根据我的习惯封装,仅供参考
查询列表
一般查询列表需要分页,同时可能包括一个搜索表单
封装
import { computed, ref, unref } from 'vue' /** * 封装获取列表 * @param {string|Ref<string>} url 请求地址 * @param {boolean} immediate 是否在初始化时加载,默认: `true` * @param {function} transform 转换搜索表单 */ export function useList({ url, transform, immediate = true }) { const page = ref(1) const limit = ref(20) const total = ref(0) const query = ref({}) // 请求上传的表单 const payload = computed(() => { const _query = transform ? transform(unref(query)) : query.value return Object.assign({}, _query, { page: page.value, // 请求的当前页字段 limit: limit.value, // 请求的条数字段 }) }) const list = ref([]) immediate && loadList() async function loadList() { // TODO: 请求api及赋值 } function refreshList() { page.value = 1 list.value = [] loadList() } return { query, page, limit, total, list, loadList, refreshList, } } 复制代码
对于 TypeScript 可以通过向 useList 传递泛型优化类型
export function useList<Item = Record<string, unknown>, Serach = Item>() { // code... } 复制代码
在 setup 中使用
const { query, // 用于绑定查询表单 page, // 用于绑定当前页 limit, // 用于绑定查询条数 total, // 记录列表总数 list, // 当前列表 loadList, // 获取列表数据 refreshList, // 用于搜索,更新列表数据 } = useList({ url: '/api/xxx' }) 复制代码
如果查询列表具有初始值,可以通过指定 immediate: false
后手动赋值 query 再加载列表
const { query, loadList } = useList({ url: '/api/xxx', immediate: false }) query.value.type = 1 loadList() 复制代码
如果需要修改查询列表参数,可以通过 transform
完成
const { query, loadList } = useList({ url: '/api/xxx', transform }) function transform(form) { const _form = { ...form } if (_form.time && _form.time.length) { _form.startDate = _form.time[0] _form.endDate = _form.time[1] _form.time = undefined } return _form } 复制代码
查询详情
查询详情一般需要指定一个id
封装
import { computed, ref, unref, watch } from 'vue' function suffixId(url, id) { // TODO: 处理url和id的关系 } /** * 封装获取详情 * @param {string|Ref<string>} url 请求地址 * @param {boolean} immediate 是否在id变动时加载,默认: `true` */ export function useDetail({ url, immediate = true }) { const id = ref('') const detail = ref({}) // 请求api的地址 const _url = computed(() => suffixId(unref(url), unref(id))) immediate && watch(id, loadDetail) async function loadDetail() { // TODO: 请求api及赋值 } return { id, detail, loadDetail, } } 复制代码
对于 TypeScript 可以通过向 useDetail 传递泛型优化类型
export function useDetail<Detail = Record<string, unknown>>() { // code... } 复制代码
在 setup 中使用
const { id, // 当前的id值 detail, // 当前id详情信息 } = useDetail({ url: '/api/xxx' }) id.value = 1 复制代码
增/改表单
增加和修改属于同类操作,一般api请求方式不同或者修改需要在地址后增加id
封装
import { computed, ref, unref } from 'vue' function suffixId(url, id) { // TODO: 处理url和id的关系 } /** * 封装表单提交 * @param {string|Ref<string>} url 请求地址 * @param {string} type 提交请求方式 post | put,默认: `post` * @param {function} transform 转换表单 */ export function useForm({ url, transform, type = 'post' }) { const form = ref({}) const payload = computed(() => { return transform ? transform(unref(form)) : form.value }) // 处理修改需要绑定id const _url = computed(() => suffixId(unref(url), form.value?.id)) async function submit(reqType = type) { if (reqType === 'post') { // TODO: 提交新增表单,使用 url } else { // TODO: 提交修改表单,使用 _url } } return { form, submit, } } 复制代码
对于 TypeScript 可以通过向 useForm 传递泛型优化类型
export function useForm<Form = Record<string, unknown>>() { // code... } 复制代码
在 setup 中使用
const { form, // 用于绑定表单 submit, // 提交表单 } = useForm({ url: '/api/xxx' }) 复制代码
如果是提交删除表单可以指定 type: 'put'
const { form, submit } = useForm({ url: '/api/xxx', type: 'put' }) // 或者 submit('put') 复制代码
通过 transform 可以在提交表单前转化表单格式同 useList
删除某项
删除时一般也是需要指定一个id,也不需要传递参数,相对比较简单就不举例了。然后你可以将删除确认弹窗封装进里面
进阶
api 管理
将api地址分散在不便于管理,这时可以单独创建一个文件管理api列表
export const Api = { xxx: '/api/xxx', // ... } 复制代码
使用时直接绑定 ({ url: Api.xxx })
动态 url
上面举例都是制定 url 为 string 类型,当然也可以直接传一个响应url,只需要在请求时解构url即可
const url = computed(() => { // ... return url }) const { list } = useList({ url }) 复制代码
export function useList({ url, transform, immediate = true }) { // ... async function loadList() { const res = await get(unref(url), payload.value) // 通过 `unref` 包裹 url // ... } // ... } 复制代码
useCrud
上面将增删改查分开,方便管理。当然都到这里了,是时候考虑重新合并了。
上面故意将 id 与 url 分离的原因就是为了在 useCrud 使用同一个 url (当然受到api接口格式影响)。如果你感兴趣,这部分就留作练习了
结语
我在日常中大量这种方式,能够减少业务页面大量重复代码,页面逻辑也更为清晰。当然组合函数可以运用到更多地方,我这里仅仅是通过业务中常见的增删改查举例,希望对你有所帮助。
示例中我隐藏与组件或请求相关的代码,可以根据具体情况灵活使用。如果有更好的方式,欢迎讨论交流
作者:tolking
链接:https://juejin.cn/post/7038182814348476452
伪原创工具 SEO网站优化 https://www.237it.com/