這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 這樣封裝列表 hooks,一天可以開發 20 個頁面 前言 在做移動端的需求時,我們經常會開發一些列表頁,這些列表頁大多數有著相似的功能:分頁獲取列表、上拉載入、下拉刷新··· 在 Vue 出來 compositionAPI 之前,我們想 ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
這樣封裝列表 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 中提供了
onPullDownRefresh
和onReachBottom
鉤子,在其中處理相關邏輯即可 - 有些列表可能不是在頁面中,而是在
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 端的幾乎只有載入更多(翻頁)的時候邏輯不太一樣,不需要拼接數據,在封裝的時候可以把分頁器的處理邏輯一起封裝進來