这样封装列表 hooks,一天能够开发 20 个页面

前言

在做移动端的需求时,我们经常会开发一些列表页,这些列表页大多数有着相似的功用:分页获取列表、上拉加载、下拉改写

Vue 出来 compositionAPI 之前,我们想要复用这样的逻辑仍是比较费事的,好在现在 Vue2.7+都支持 compositionAPI语法了,这篇文章我将 手把手带你用 compositionAPI 封装一个名为 useList hooks来完成列表页的逻辑复用。

基础版

需求剖析

一个列表,最根本的需求应该包括: 建议恳求,获取到列表的数组,然后将该数组渲染成相应的 DOM 节点。要完成这个功用,我们需求以下变量:

  • list : 数组变量,用来寄存后端回来的数据,并在 template模板中运用 v-for来遍历渲染成我们想要的样子。
  • listReq: 建议 http 恳求的函数,一般是 axios的实例

代码完成

有了上面的剖析,我们能够很轻松地在 setup中写出如下代码:

import { ref } from 'vue'
import axios from 'axios' // 简略示例,就不给出封装axios的代码了
const list = ref([])
const listReq = () => {
  axios.get('/url/to/getList').then((res) => {
    list.value = res.list
  })
}
listReq()

这样,我们就完成了一个根本的列表需求的逻辑部分。大部分的列表需求都是相似的逻辑,既然如此,Don't Repeat Yourself!(不要重复写你的代码!),我们来把它封装成通用的办法:

  • 首要,既然是通用的,会在多个当地运用,那么数据必定不能乱了,我们要在每次运用 useList的时分都拿到独归于自己的那一份数据。是不是感觉很熟悉?对的,就是曾经的 data为什么是一个函数那个问题!所以我们的 useList是需求导出一个函数,我们从这个函数中获取数据与办法。让这个函数导出一个目标/数组,这样调用的时分 解构就能够拿到我们需求的变量和办法了
// useList.js 中
const useList = () => {
  // 待弥补的函数体
  return {}
}
export default useList
  • 然后,不同的当地调用的接口必定不相同,我们想一次封装,不再维护,那么我们干脆在运用的时分,把调用接口的办法传进来就能够了
// useList.js 中
import { ref } from 'vue'
const useList = (listReq) => {
  if (!listReq) {
    return new Error('请传入接口调用办法!')
  }
  const list = ref([])
  const getList = () => {
    listReq().then((res) => (list.value = res.list))
  }
  return {
    list,
    getList,
  }
}
export default useList

这样,我们就完成了一个简略的列表 hooks,运用的时分直接:

// setup中
import useList from '@/utils'
const { list, getList } = useList(axios.get('url/to/get/list'))
getList()

等等!列表好像不涉及到 DOM操作,那我们再偷点懒,直接在 useList内部就调用了吧!

// useList.js中
import { ref } from 'vue'
const useList = (listReq) => {
  if (!listReq) {
    return new Error('请传入接口调用办法!')
  }
  const list = ref([])
  const getList = () => {
    listReq().then((res) => (list.value = res.list))
  }
  getList() // 直接初始化,省去在外面初始化的进程
  return {
    list,
    getList,
  }
}
export default useList

这时有老哥要说了,那我要是一个页面有多个列表怎么办?嘿嘿,别忘了,解构的时分是能够重命名的

// setup中
const { list: goodsList, getList: getGoodsList } = useList(
  axios.get('/url/get/goods')
)
const { list: recommendList, getList: getRecommendList } = useList(
  axios.get('/url/get/goods')
)

这样,我们就一同在一个页面里面,获取到了产品列表以及推荐列表所需求的变量与办法啦

带分页版

假如数据量比较大的话,一切的数据全部拿出来渲染显然不合理,所以我们一般要进行分页处理,我们来剖析一下这个需求:

需求剖析

  • 要分页,那我们必定要告知后端当前恳求的是第几页、每页多少条,或许有些当地还需求展示总共有多少条,为了方便办理,我们把这些分页数据统一放到 pageInfo目标中
  • 分页了,那我们必定还有加载下一页的需求,需求一个 loadmore函数
  • 分页了,那我们必定还会有改写的需求,需求一个 initList函数

代码完成

需求剖析好了,代码完成起来就简略了,废话少说,上代码!

// useList.js中
import { ref } from 'vue'
const useList = (listReq) => {
  if (!listReq) {
    return new Error('请传入接口调用办法!')
  }
  const list = ref([])
  // 新增pageInfo目标保存分页数据
  const pageInfo = ref({
    pageNum: 1,
    pageSize: 10,
    total: 0,
  })
  const getList = () => {
    // 分页数据作为参数传递给接口调用函数即可
    // 将恳求这个Promise回来出去,以便链式then
    return listReq(pageInfo.value).then((res) => {
      list.value = res.list
      // 更新总数量
      pageInfo.value.total = res.total
      // 回来出去,交给then默许的Promise,以便后续运用
      return res
    })
  }
  // 新增加载下一页的函数
  const loadmore = () => {
    // 下一页,那我们把当前页自增一下就行了
    pageInfo.value.pageNum += 1
    // 假如已经是最终一页了(本次获取到空数组)
    getList().then((res) => {
      if (!res.list.length) {
        uni.showToast({
          title: '没有更多了',
          icon: 'none',
        })
      }
    })
  }
  // 新增初始化
  const initList = () => {
    // 初始化一般是要把一切的查询条件都初始化,这里只要分页,咱就回到第一页就行
    pageInfo.value.pageNum = 1
    getList()
  }
  getList()
  return {
    list,
    getList,
    loadmore,
    initList,
  }
}
export default useList

完工!跑起来试试,Perfec……等等,好像不太对…

加载更多,应该是把两次恳求的数据合并到一同渲染出来才对,这怎么直接替换掉了?

回头看看代码,原来是我们漏了拼接的逻辑,补上,补上

// useList.js中
// ...省掉其他代码
const getList = () => {
  // 分页数据作为参数传递给接口调用函数即可
  return listReq(pageInfo.value).then((res) => {
    // 当前页不为1则是加载更多,需求拼接数据
    if (pageInfo.value.pageNum === 1) {
      list.value = res.list
    } else {
      list.value = [...list.value, ...res.list]
    }
    pageInfo.value.total = res.total
    return res
  })
}
// ...省掉其他代码

带 hooks 版

上面的分页版,我们给出了 加载更多初始化列表功用,可是仍是要手动调用。仔细想想,我们改写列表,一般都是在页面顶部下拉的时分改写的;而加载更多,一般都是在翻滚到底部的时分加载的。既然都是相同的触发机遇,那我们持续封装吧!

需求剖析

  • uni-app 中供给了 onPullDownRefreshonReachBottom钩子,在其中处理相关逻辑即可
  • 有些列表或许不是在页面中,而是在 scroll-view中,仍是需求手动处理,因此上面的函数我们仍然需求导出

代码完成

钩子函数(hooks)接受一个回调函数作为参数,我们直接把上面的函数传入即可

需求注意的是,uni-app 中,下拉改写的动画需求手动封闭,我们还需求改造一下 listReq函数


// useList中
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
// ...省掉其他代码
onPullDownRefresh(initList)
onReachBottom(loadmore)
const getList = () => {
  // 分页数据作为参数传递给接口调用函数即可
  return listReq(pageInfo.value)
    .then((res) => {
      // ...省掉其他代码
    })
    .finally((info) => {
      // 不管成功仍是失败,封闭下拉改写的动画
      uni.stopPullDownRefresh()
      // 在最终再把前面回来的音讯return出去,以便后续处理
      return info
    })
}
// ...省掉其他代码

带参数

其实在实际开发中,我们在建议恳求时或许还需求其他的参数,上面我们都是固定的只要分页的参数,能够稍加改造

需求剖析

或许我们第一反应是多一个参数,或许用 展开运算符 (…)再界说一个形参就行了。这么做必定是没问题的,不过在这里的话不够高雅~

我们这里是要增加一个传给后端的参数,一般都是一同以 JSON 目标的形式传过去,既然如此,那我们把一切的参数都用一个目标接受,建议恳求的时分和分页参数目标合并为一个目标,代码的可读性会更高,运用者在运用时也能够自由地界说 key-value 键值对

代码完成

// useList中
const useList = (listReq, data) => {
  // ...省掉其他代码
  // 判断第二个参数是否是目标,以免后面运用展开运算符时报错
  if (data && Object.prototype.toString.call(data) !== '[object Object]') {
    return new Error('额定参数请运用目标传入')
  }
  const getList = () => {
    const params = {
      ...pageInfo.value,
      ...data,
    }
    return listReq(params).then((res) => {
      // ...省掉其他代码
    })
  }
  // ...省掉其他代码
}
// ...省掉其他代码

带默许装备版

有些时分我们的列表是在页面中心,不需求触底加载更多;有时分我们或许需求在不同的当地调用相同的接口,可是需求获取的数据量不相同….

为了习惯各种各样的需求,我们能够稍加改造,增加一个带有默许值的装备目标,

// useList.js中
const defaultConfig = {
  pageSize: 10, // 每页数量,其实也能够在data里面覆盖
  needLoadMore: true, // 是否需求下拉加载
  data: {}, // 这个就是给接口用的额定参数了
  // 还能够依据自己项目需求增加其他装备
}
// 增加一个有默许值的参数,仍然满意大部分列表页传入接口即可运用的需求
const useList = (listReq, config = defaultConfig) => {
  // 解构的时分赋上初始值,这样即使装备参数只传了一个参数,也不影响其他的装备
  const {
    pageSize = defaultConfig.pageSize,
    needLoadMore = defaultConfig.needLoadMore,
    data = defaultConfig.data,
  } = config
  // 应用相应的装备
  if (needLoadMore) {
    onReachBottom(loadmore)
  }
  const pageInfo = ref({
    pageNum: 1,
    pageSize,
    total: 0,
  })
  // ...省掉其他代码
}
// ...省掉其他代码

这样一来,我们就完成了一个满意大部分移动端列表页的逻辑复用 hooks

web 端的几乎只要加载更多(翻页)的时分逻辑不太相同,不需求拼接数据,在封装的时分能够把分页器的处理逻辑一同封装进来

总结

在这篇文章中,我们从需求剖析开始,到代码要害逻辑剖析,再到完成后的 bug 修复,再到功用扩展,根本完整地复现了编码的考虑进程,期望能给我们带来一些收成~

一同,欢迎我们在谈论区和谐讨论~