目录
  • 一、来源
    • 列表页面+下拉刷新和上拉加载+占位图(无数据+断网)…
  • 二、使用示例
    • 最后、总结

      一、来源

      之前做 vue 手机端需求开发时,遇到多个相似列表页面:

      列表页面+下拉刷新和上拉加载+占位图(无数据+断网)…

      就想能不能复用同一个页面,传入不同的数据和单元格即可;经过一个多月的思考(查询js 的特性 + Vue3官方文档),最终在官方文档:

      Vue3 重构函数透传示例解析

      找到解决办法: 属性透传方法

      原理如下:

      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 重构函数透传的资料请关注其它相关文章!

      声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。