讲解在同名B/D上都有,主要介绍一些跟业务无关的代码技巧
注: 部分内容主观性较大,一家之言姑且听之
本文主要介绍tree组件
使用时,如何做数据转换,只是方案的探讨,并没有详细封装
数据格式预览
- 后端给的数据
[
{
"id": "2",
"name": "河南省",
"parentId": 1
},
{
"id": "3",
"name": "郑州市",
"parentId": "2"
}
]
- tree 组件要的数据
[
{
"id": "1734464623572807681",
"name": "河南省",
"parentId": 1,
"children": [
{
"id": "1735863930750361602",
"name": "郑州市",
"parentId": "1734464623572807681"
}
]
}
]
根正苗红篇 扁平转换嵌套算法
纯八股
function buildHierarchy(data) {
// 创建一个对象,用于快速查找节点
const nodeMap = {};
// 遍历数据集,创建节点对象
for (const node of data) {
nodeMap[node.id] = {
...node,
children: [],
};
}
// 遍历数据集,将子节点添加到父节点的 children 数组中
for (const node of data) {
const parent = nodeMap[node.parentId];
if (parent) {
parent.children.push(nodeMap[node.id]);
}
}
// 返回根节点
return Object.values(nodeMap).filter((node) => !nodeMap[node.parentId])
}
前端模型
惰性加载: 点击时,才处理
不能跟上面拼效率,核心是惰性加载
当然,对于组件实现而言,必然会在渲染页面时,使用到item.children[使用后会立马计算],这种惰性加载的方式,需要额外处理
这里先不处理
/**
* 模型数据 - 惰性加载的方式
*/
function buildHierarchy2(data) {
function getChildren() {
const id = this.id
// 可以使用缓存优化
return data.filter(item => item.parentId === id)
}
return data
// 每次点击的时候请求
.map(item => Object.defineProperty(item, 'childrenAsyc', {
get: getChildren
}))
.filter((node) => node.parentId == 1)
}
核心数据优先加载: 第一次加载第一层,而后立马加载第二层数据
数据加载优化的基础逻辑
- 优先加载核心数据
- 异步加载非核心数据
我们可以使用访问器属性的特性,实现加载核心数据[第一层数据],而后立马加载第二层依赖的数据[预加载]
function buildHierarchy2(data) {
// 1. 加载完成
// 2. 正在加载中
const mapping = {
}
function getChildren(item) {
const id = item.id
// 缓存优化
// return data.filter(item => item.parentId === id)
return new Promise((resolve) => {
// 预加载缓存优化
// 此处少写了一种可能,正在通讯中,先忽略
if (mapping[id]) {
resolve(mapping[id])
} else {
// 接口聚合操作
setTimeout(() => {
mapping[id] = data.filter(item => item.parentId === id)
resolve(mapping[id])
}, 1000)
}
})
}
return data
// 核心数据 - 预加载
.map(item => Object.defineProperty(item, 'children', {
get: () => getChildren(item)
}))
.filter((node) => node.parentId == 1)
}
接口合并
当然,上面的问题是会在同一时间,产生大量的网络任务,接口合并即可,一个简单的超级接口,方案略
附:element-ui下的代码
<template>
<div id="app">
<el-tree lazy :load="loadHandler" :props="{ label: 'name' }"></el-tree>
</div>
</template>
<script>
import data from './1.json'
/**
* @description 将扁平化的变成嵌套的
* @param data
* @returns
*/
function buildHierarchy(data) {
// 创建一个对象,用于快速查找节点
const nodeMap = {};
// 遍历数据集,创建节点对象
for (const node of data) {
nodeMap[node.id] = {
...node,
children: [],
};
}
// 遍历数据集,将子节点添加到父节点的 children 数组中
for (const node of data) {
const parent = nodeMap[node.parentId];
if (parent) {
parent.children.push(nodeMap[node.id]);
}
}
// 返回根节点
return Object.values(nodeMap).filter((node) => !nodeMap[node.parentId])
}
/**
* 模型数据 - 惰性加载的方式
* 描述 异步模型
*/
function buildHierarchy2(data) {
// function getChildren() {
// const id = this.id
// // 缓存优化
// // return data.filter(item => item.parentId === id)
// console.log('getChildren', id)
// return new Promise((resolve) => {
// setTimeout(() => {
// resolve(data.filter(item => item.parentId === id))
// }, 1000)
// })
// }
// 1. 加载完成
// 2. 正在加载中
const mapping = {
}
function getChildren(item) {
const id = item.id
console.log('getChildren', id)
// 缓存优化
// return data.filter(item => item.parentId === id)
return new Promise((resolve) => {
// 预加载缓存优化
if (mapping[id]) {
resolve(mapping[id])
} else {
// 接口聚合操作
setTimeout(() => {
mapping[id] = data.filter(item => item.parentId === id)
resolve(mapping[id])
}, 1000)
}
})
}
return data
// 每次点击的时候请求
// .map(item => Object.defineProperty(item, 'childrenAsyc', {
// get: getChildren
// }))
// 核心数据 - 预加载
.map(item => Object.defineProperty(item, 'children', {
get: () => getChildren(item)
}))
.filter((node) => node.parentId == 1)
}
export default {
data() {
return {
data: buildHierarchy2(data)
}
},
methods: {
loadHandler(node, resolve) {
if (node.level === 0) {
resolve(this.data)
} else {
node.data.children?.then(items => resolve(items))
}
}
}
}
</script>