记录–这样封装列表 hooks,一天可以开发 20 个页面

  • 记录–这样封装列表 hooks,一天可以开发 20 个页面已关闭评论
  • 122 次浏览
  • A+
所属分类:Web前端
摘要

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


这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

记录--这样封装列表 hooks,一天可以开发 20 个页面

这样封装列表 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 端的几乎只有加载更多(翻页)的时候逻辑不太一样,不需要拼接数据,在封装的时候可以把分页器的处理逻辑一起封装进来

本文转载于:

https://juejin.cn/post/7165467345648320520

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 记录--这样封装列表 hooks,一天可以开发 20 个页面