Vue3重构函数透传示例解析
一、来源
之前做 vue 手机端需求开发时,遇到多个相似列表页面:
列表页面+下拉刷新和上拉加载+占位图(无数据+断网)...
就想能不能复用同一个页面,传入不同的数据和单元格即可;经过一个多月的思考(查询js 的特性 + Vue3官方文档),最终在官方文档:
找到解决办法: 属性透传方法
原理如下:
二、使用示例
1、VRefreshListDemo.vue
<template>
<navbar
class="navbar"
isleftarrow
navBarTitle="数据透传"
closeWebview
isFixed
/>
<VRefreshList
:requestFC="requestFC"
:requestErrorFC="requestErrorFC"
:requestParamsChange="tag"
:pageIndexInitial="pageIndexInitial"
>
<template v-slot="slotProps">
<MessageInteractiveCell
class="page-view__cell"
v-for="(e, index) in slotProps.list" :key="index"
:imgUrl="deliverDataObj(e)?.activityUserIcon"
:imgUrlRight="getResizedAliOSSImageUrl(e, 64)"
:text="formatContentTitle(e)"
:detailText="formatContentDetails(e)"
:tag="formatDateNoYear(e.pushTime)"
@click="clickCell(e)"
>
</MessageInteractiveCell>
</template>
</VRefreshList>
</template>
<script setup>
import navbar from '@/components/navbar.vue';
import VRefreshList from './VRefreshList.vue';
import MessageInteractiveCell from '@/views/message/components/MessageInteractiveCell.vue';
import { getCurrentInstance, ref, reactive, computed, watch, onMounted, onActivated} from 'vue';
import { onBeforeRouteLeave } from 'vue-router';
import { Toast } from 'vant';
import { useStore } from 'vuex';
import { DropdownMenu, DropdownItem } from 'vant';
import { NET_MSG_ERROR } from '@/service/request/apiMessage';
import { hasNet, goToPage, goToPageForResult } from '@/utils/uplusApi';
import * as RQ from '@/service/request/request.js';
import * as MessageContant from '@/views/message/MessageContant.js';
import {
formatDateWithYear,
formatDateNoYear,
jumpURL,
getResizedAliOSSImageUrl,
deliverDataObj,
} from '@/views/message/MessageCommonUtil';
import icon_interactive from '@/assets/images/icon_interactive_base64';
import icon_like from '@/assets/images/icon_like_base64';
import icon_evaluation from '@/assets/images/icon_evaluation_base64';
const store = useStore();
const currentInstance = getCurrentInstance();
const { $platform, $vtoast, $debounce, $gio} = currentInstance.appContext.config.globalProperties;
onBeforeRouteLeave(() => {
$vtoast.clear();
});
// 初始创建页面刚进来加载一次
onMounted(() => {
// console.log('MessageList -> onMounted');
// $vtoast.loading({});
// onRefresh();
});
onActivated(() => {
console.log('ContentList.vue -> onActivated');
// $vtoast.loading({});
// onMore();
});
const businessType = ref(store.getters.msgType || '0');
const pageIndexInitial = ref(1);
/// 获取历史消息
async function requestFC(options) {
const {isRefresh, page, pageSize, lastObj} = options;
console.log(`VRefreshListDemo requestFC:${JSON.stringify(isRefresh)},${page}`);
const timestamp = Date.now();
let msgTime = formatDateWithYear(timestamp);
if (!isRefresh && lastObj) {
msgTime = lastObj.pushTime;
}
const params = {
msgTime: msgTime,
queryTag: 1,
querySize: pageSize,
businessType: businessType.value,
};
if (['20'].includes(businessType.value) &&
store.getters.msgBelong &&
store.getters.msgBelongType) {
params.belong = store.getters.msgBelong;
params.belongType = store.getters.msgBelongType;
}
const items = await RQ.getMsgHistory(params);
return items;
}
async function requestErrorFC(error) {
Toast(JSON.stringify(error));
}
const clickCell = (obj) => {
if (!hasNet.value) {
Toast(NET_MSG_ERROR);
return;
}
const url = formatContentReviewPage(obj);
jumpURL(url);
};
function formatContentTitle(e) {
const activityUserObj = deliverDataObj(e);
// const result = decodeURIComponent(activityUserObj?.activityUserNickName ?? '');
const result = decodeURIComponent(e.message.data.body.view?.title ?? '-');
return result;
}
function formatContentDetails(e) {
const result = decodeURIComponent(e.message.data.body.view?.title ?? '-');
return result;
}
function formatContentReviewPage(e) {
const result = e.message.data.body.extData?.reviewPage;
return result;
}
</script>
<style scoped lang='scss'>
.page-view{
position: relative;
margin-top: 46px;
height: calc(100vh - 46px);
overflow: scroll;
}
.navbar{
height: 46px;
}
:deep .van-pull-refresh{
top: 46px;
}
</style>
复制代码
2、VRefreshList.vue 源码
VRefreshCustom 是“上拉刷新和下拉加载+占位图”的封装
<template>
<VRefreshCustom
v-model:refreshing="refreshing"
v-model:loading="loading"
:onRefresh="onRefresh"
:loadMore="onMore"
:finished="finished"
:loadingText="loadingText"
:finishedText="finishedText"
:netStatus="netStatus"
:clickVNet="clickVNet"
:isSuccess="isSuccess"
>
<slot :list="list"></slot>
</VRefreshCustom>
</template>
<script setup>
import VRefreshCustom from './VRefreshCustomNew.vue';
import { getCurrentInstance, ref, reactive, computed, watch, onMounted, onActivated} from 'vue';
import { onBeforeRouteLeave } from 'vue-router';
import { Toast } from 'vant';
import { NET_MSG_ERROR } from '@/service/request/apiMessage';
import { hasNet, goToPage, goToPageForResult } from '@/utils/uplusApi';
const currentInstance = getCurrentInstance();
const { $platform, $vtoast, } = currentInstance.appContext.config.globalProperties;
onBeforeRouteLeave(() => {
$vtoast.clear();
console.log('离开 MessageInteractivePage.vue');
});
const props = defineProps({
requestFC: {
type: Function,
required: true,
description: '接口请求方法',
},
requestErrorFC: {
type: Function,
description: '接口请求错误回调方法',
},
pageIndexInitial: {
type: Number,
default: 1,
},
pageSize: {
type: Number,
default: 30,
},
requestParamsChange: {
type: String,
},
loadingText: {
type: String,
default: '',
},
finishedText: {
type: String,
default: '',
},
});
const pageIndex = ref(props.pageIndexInitial);
const refreshing = ref(false);
const isSuccess = ref(false);
const loading = ref(false);
const finished = ref(false);
/// -1 请求失败; 0无数据; 1 正常列表,有数据;
const netStatus = ref(1);
const list = reactive([]);
const clickVNet = () => {
$vtoast.loading({});
onRefresh();
};
///下拉刷新
const onRefresh = async () => {
console.log('VRefreshList onRefresh');
pageIndex.value = props.pageIndexInitial;
if (!hasNet.value) {
// netStatus.value = -1;
refreshing.value = false;// 下拉刷新加载状态结束
isSuccess.value = false;// 下拉刷新成功失败
loading.value = false;// 加载状态结束
finished.value = true;// 数据全部加载完成
$vtoast.clear();
Toast(NET_MSG_ERROR);
return;
}
refreshing.value = true;
loading.value = false;
requestList(true);
};
// 上拉获取更多
const onMore = async () => {
console.log('VRefreshList onMore');
pageIndex.value++;
refreshing.value = false;
loading.value = true;
requestList(false);
};
/// 获取历史消息
const requestList = async () => {
try {
netStatus.value = 1;
const params = {
isRefresh: refreshing.value,
page: pageIndex.value,
pageSize: props.pageSize,
lastObj: list.length > 0 ? list[list.length - 1] : undefined,
};
const items = await props.requestFC(params);
// console.log(`VRefreshList items:${items.length}`);
if (refreshing.value) {
list.splice(0, list.length);
}
if (list.length === 0 && items.length === 0) {
refreshing.value = false;
isSuccess.value = true;
loading.value = false;// 加载状态结束
finished.value = true;// 数据全部加载完成
netStatus.value = 0;
return;
}
if (items.length) {
list.push(...items);
}
console.log(`${location.hash} list:${list.length}, items:${items.length}`);
loading.value = false;// 加载状态结束
finished.value = (items.length < props.pageSize);// 数据全部加载完成
if(refreshing.value){
isSuccess.value = true;
}
netStatus.value = list.length === 0 ? 0 : 1;
} catch (error) {
console.log(`${location.hash} error: ${JSON.stringify(error)},
refreshing: ${refreshing.value},
hasNet.value:${hasNet.value}`);
finished.value = true;// 数据全部加载完成
isSuccess.value = false;// 下拉刷新成功失败
if (!hasNet.value) {
netStatus.value = -1;
} else {
netStatus.value = 0;
}
if (!error) {
return;
}
props.requestErrorFC && await props.requestErrorFC(error);
} finally {
$vtoast.clear();
refreshing.value = false;
loading.value = false;// 加载状态结束
}
};
watch(() => props.requestParamsChange, (newValue, oldValue) => {
console.log('watch requestParamsChange', newValue, oldValue);
if (newValue !== oldValue) {
scrollToTop(listEl);
onRefresh();
}
});
watch(() => hasNet.value, (newValue, oldValue) => {
// console.log('hasNet.value', newValue, oldValue);
if (newValue) {
onRefresh();
}
});
let listEl;
onMounted(() => {
listEl = document.getElementById('van-list');
// console.log(listEl);
});
const scrollToTop = (e) => e.scrollIntoView({ block: 'start' });
</script>
复制代码
最后、总结
1、此文重构方法的意义在于我可以通过封装一个列表组件,然后同类页面只需要传请求方法即可,极大的提高开发效率(vue 中组件亦可是页面);
2、核心是通过 Vue3 属性支持 Function 进而实现函数透传,虽然官方文档只是一笔带过,但确实是核心特性之一;
3、Function 类型无论在 Swift、Dart/Flutter、JS/TS 中都是一等公民;可以帮助我们扩展代码重构边界。在实现此次封装之前我也不知道能做到什么程度,不过最终效果非常理想;
4、姐妹篇 Flutter 重构: 属性透传/函数透传
以上就是Vue3 重构函数透传示例解析的详细内容,更多关于Vue3 重构函数透传的资料请关注编程网其它相关文章!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341