日常开发阶段性总结20250718

63 阅读4分钟

列表性能优化

之前使用的div 包括组件的方式去展示一个可滚动的动态表单列表。 数据量少的时候,看不出啥,等测试批量导入一波数据后,结果发现加载非常缓慢,而且打开关闭modal几次后浏览器就会out of memory 页面崩溃。


1.问题排查
--首先发现有个银行的下拉数据非常庞大,在同事的指引下,发现这种数据量很大的下拉一定需要开启虚拟列表滚动,基本上市面上的组件库都有这个功能

2.如标题,解决办法还是虚拟滚动。这里把直接v-for div容器,然后填充组件的方式改为:a-list 然后开启:virtual-list-props="{}" 这种方式。这样两种方式处理之下,内存占用率极大的降低了
<a-list
  ref="transListRef"
  :data="formData.bank_trans"
  :virtual-list-props="{
    height: 560,
    buffer: 5
  }"
  :bordered="false"
  :split="true"
  :buffer="5"
 
>
  <template #item="{ item, index }">
    <a-list-item :key="index">
      <div class="flex flex-sp-bt flex-y-center grey-title">
        <div>
        title
        </div>
        <div class="del-btn" @click="onDelYhzz(index, item)">删除</div>
      </div>
      <!---->
      <BankTransfer
        :ref="(el) => setBankTFRef(el, index)"
        v-model:data="formData.bank_trans[index]"
      />
    </a-list-item>
  </template>
</a-list>


acro-design a-table 中自定义表头

<template #th="{ column }">
  <div>12311</div>
  <MyTh :data="column" /> 如果是这样就会表头异常
</template>

image.png

<template #th="{ column }"> // 必须是传入单一空元素,像这样
  <MyTh :data="column" />
</template>


监听子组件是否数据完全加载并渲染出来

1.第一步,子组件内暴露一个flag
2.给子组件加上ref
3.父组件内监听判断

watch(
  () => comRef.value, // 监听子组件ref是不是获取到数据,在判断flag
  async (newVal) => {
    if (
      newVal &&
      (newVal.transFlaga || newVal.cashFlaga || newVal.otherFlaga) &&
      !views.value[curView.value].visited
    ) {
      views.value[curView.value].visited = true;
      newVal.initData();
      await nextTick();
      showSkeleton.value = false; // 隐藏骨架屏
    }
  },
  { deep: true, immediate: true }
);

动态表单校验问题

<a-form
  ref="formRef"
  :model="formData"
  layout="vertical"
  :scroll-to-first-error="true"
  style="flex: 1"
> 


<a-form-item
  :field="`${dataName}.${index}.account`"


//对于动态表单校验问题, 主要依赖于:field="`a.0.b`"。 这表示 校验a数组下的第一个元素的b属性有没有值
比如你 :model="formData" 绑定的formData的数据为:{a:[{b:1}]} ,这就会校验通过

全局引用

import confirmCustom from "@/components/confirm-modal/index.js";
import { Message, Modal } from "@arco-design/web-vue";
import Dayjs from "dayjs";
import { debounce, throttle } from "lodash";

Modal.confirmCustom = confirmCustom;

class PluginInstall {
  constructor() {
    this.install = (app) => {
      this.initPlugin(app);
    };
    this.initPlugin = (app) => {
    // 后面子组件使用直接inject就可以了
      app.provide("$message", Message);
      app.provide("$confirm", Modal);
      app.provide("dayjs", Dayjs);
      app.provide("lodash", this.getLodash());
    };
    this.getLodash = () => {
      return { throttle, debounce };
    };
  }
}

const pluginInstall = new PluginInstall();
export default pluginInstall;

对于弹窗第一次数据不展示

<script setup>
import { computed } from "vue";
import { get } from "lodash";

const props = defineProps({
  data: {
    type: Object,
    default: () => ({})
  },
  fields: {
    type: Array,
    default: () => []
  },
  columns: {
    type: Array,
    default: () => []
  },
  api: {
    type: String,
    default: () => ""
  }
});

const fields = computed(() => { // 这种写法 第一次数据展示不出来, 因为
- 问题在于:第一次计算时,`props.data`是一个空对象,所以实际上我们访问了`props.data`的哪些属性?是`props.fields`中每个item的key。如果`props.fields`是一个空数组,

那么就不会访问`props.data`的任何属性。那么,当`props.data`后来被赋值为一个非空对象时,由于computed函数在第一次执行时并没有访问到`props.data`的任何具体属性(或者访问到了,

但是当时没有这个属性,所以没有建立依赖?),所以当`props.data`改变(比如从空对象变为有数据的对象)时,computed不会重新计算。

- 因此,当`props.data`被更新时,computed不会重新执行,所以fields还是第一次计算的结果(即value为`-`),而不会更新为新的`props.data`中的值。

  let list = get(props, "fields", []);
  list.forEach((item) => {
    item.value = props.data[item.key] || "-";
  });
  return list;
});
</script>
const list = ref([]);
// 换成这种写法就展示出来了
watch(
  () => props.data,
  (val) => {
    if (val.id) {
      list.value = props.fields.map((item) => ({
        ...item,
        value: val[item.key] || "-"
      }));
    }
  }
);

用tabs组件, 最好只用导航功能,内容用component 配合切换最好,更可控

<a-tabs v-model:active-key="ackey" @change="onTabChange">
  <a-tab-pane
    v-for="item in tabsList"
    :key="item.key"
    :title="item.label"
  />
</a-tabs>
<component
  :is="componentMap[tabValue].component"
  :fields="componentMap[tabValue].fields"
  :columns="componentMap[tabValue].columns"
  :api="componentMap[tabValue].api"
  :data="data"
/>

component 就可以当作是普通组件传props

<component
  :is="componentMap[tabValue].component"
  :fields="componentMap[tabValue].fields"
  :columns="componentMap[tabValue].columns"
  :api="componentMap[tabValue].api"
  :data="data"
/>

router-view 配合 keep-alive 缓存组件状态


<router-view
  v-slot="{ Component }"
  :key="state.num"
  v-loading="state.viewload"
>
  <keep-alive :include="allRouteName">
    <component :is="Component" :key="state.componentKey" />
  </keep-alive>
</router-view>

就是要注意如果组件没有name属性, 是无法被缓存的!!!

还有一种写法:就是不需要额外写一个script标签

npm i vite-plugin-vue-setup-extend -D

然后在vite.config.ts中引入这个插件就可以使用了


import { defineConfig, Plugin } from "vite";
import vue from "@vitejs/plugin-vue";
import vueSetupExtend from "vite-plugin-vue-setup-extend";

export default defineConfig({
  plugins: [vue(), vueSetupExtend()],
});

然后就可以这样命名了

<script lang="ts" setup name="A"></script>
export default {
  name: ROUTE_NAME_MAP.electricFraud // 这样就可以被缓存!!!
};
</script>

v-for 循环出来的数据,如果删除某一个元素,但是视图一下子删除很多个,极有可能是key重复了,vue将重复的key会当作一个元素一起删除

对于批量数据的展示,借助于一些 description 列表组件时,比较好用的技巧:

先提前定义好数据结构,包括title,和key
export const confirmInfoList = [
  {
    label: "1",
    value: "",
    key: "verify_status_str"
  },
  {
    label: "2",
    value: "",
    key: "ack_at"
  },
  {
    label: "3",
    value: "",
    key: "unfraud_reason"
  }
];
// 然后就可以遍历批量处理这个数据展示
const confirmInfo = computed(() => {
  let list = cloneDeep(confirmInfoList);
  list.forEach((item) => {
    let cInfo = get(state.value.formData, "ack_info", {});
    item.value = cInfo[item.key] || "-";
  });
  return list;
});

表格数据获取hooks封装

import { cloneDeep } from "lodash";
import { reactive, ref } from "vue";

export default () => {
  /* 表格数据*/
  const tableData = ref([]);
  const tableLoading = ref(false);
  /* 筛选参数*/
  const filterParams = ref({});
  /* 点击搜索后缓存筛选参数*/
  const cacheFilterParams = ref({});
  /* 选择行*/
  const selectedKeys = ref([]);
  /* 分页参数*/
  const page = reactive({
    page: 1,
    per_page: 10,
    total: 0
  });
  /* 选择行配置*/
  const rowSelection = reactive({
    type: "checkbox",
    showCheckedAll: true,
    onlyCurrent: true,
    fixed: true
  });
  /* 搜索*/
  const search = (getList) => {
    page.page = 1;
    cacheFilterParams.value = cloneDeep(filterParams.value);
    getList();
  };

  /* 搜索*/
  const reset = (getList) => {
    filterParams.value = {};
    search(getList);
  };

  const getApiList = (api, searchParams = {}, callback) => {
    tableLoading.value = true;
    api(searchParams)
      .then((res) => {
        callback(res);
      })
      .finally(() => {
        tableLoading.value = false;
      });
  };

  return {
    tableData,
    tableLoading,
    page,
    rowSelection,
    filterParams,
    cacheFilterParams,
    selectedKeys,
    search,
    reset,
    getApiList
  };
};