给你说个这辈子没见过的tree嵌套的模型

67 阅读3分钟

讲解在同名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>