列表性能优化
之前使用的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>
<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
};
};