原生JS使用递归制作树形结构和相关知识点

目录

1.分析数据结构

2.原生js数据请求

3.递归算法(关键)

4.一键展开/隐藏

5.单独点击没一个树杈,能做到展开/隐藏下面的子元素

6.完成搜索框(核心仍为递归算法)


最近要做一个只用原生js递归制作一个树形结构的小dom

记录一下写dom的过程

1.分析数据结构

根据接口,我这边的数据结构类型是这样的

            let data = [{
                child: [{
                    child: [{
                        child: [],
                        id: 3,
                        mobile: "18800000008",
                        username: "GWCQ444"
                    }],
                    id: 2,
                    mobile: "18800000007",
                    username: "GWCQ555"
                }],
                id: 1,
                mobile: "18800000006",
                username: "GWCQ666"
            }]

很典型的需要递归拆解的数组结构,child里没有元素的时候是空数组

2.原生js数据请求

原生js的数据请求方式是原生的阿贾克斯,也就是XMLHttpRequest()这个东西

渲染界面首先要拿到数据,所以js一开始就要做数据请求

            let xhr = new XMLHttpRequest();
            let data = []
            xhr.open('GET', blobUrl, true); //blobUrl是请求接口
            xhr.send();
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    data = JSON.parse(xhr.responseText).data;
                }
            }

因为所有对数据的操作都是在这个请求之后拿到数据才能进行的,所以所有操作都要写在请求成功之后,就是xhr.status == 200 这个判断成功之后,不是很美观,小封装一下代码(当然也可以不封装,如果请求接口少的话也无所谓封装不封装了)

封装成函数

    function getUserData(blobUrl) {
        return new Promise((resolve, reject) => {
            let xhr = new XMLHttpRequest();
            xhr.open('GET', blobUrl, true);
            xhr.send();
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    resolve(JSON.parse(xhr.responseText).data);
                }
            }
        });
    }

利用一个Promise进行封装,这样写起来就看着不至于冗杂了

然后完成第一步,拿到数据

3.递归算法(关键)

拿到数据之后就是经典递归算法

这里用ul 和 li 渲染数据

    <div class='lists'></div>
        .lists {
            margin-top: 20px;
        }

        li {
            margin-bottom: 5px;
            border-left: 1px solid #000;
            cursor: pointer; /*禁止选中*/
            padding: 10px 0;
        }

直接上递归算法

    function tree(data) {
        var str = "<ul style='padding-left:20px;'>";
        for (var i = 0; i < data.length; i++) {
            // str += "<li>" + data[i].username;
            str += "<li style='display:none' id=" + data[i].id + ">" + data[i].username + '   id=' + data[i].id;
            if (data[i].child) {
                str += tree(data[i].child)
            }
            str += "</li>";
        }
        str += "</ul>";
        return str;
    }

 递归算法的核心在于,如果data[i].child存在元素,则说明树叉下面还能继续分,则调用函数本身,继续往下分

这里给li绑定id,可以做查询展开之后的锚点定位

document.querySelector(".lists").innerHTML = tree(res);

一行代码渲染上去

到这里这个dom的主体就这样完成了,剩下的就是小功能拓展

4.一键展开/隐藏

刚开始的版本是想简单做一个一键展开或者隐藏的操作,但是觉得功能单一,还是展开和隐藏的按键分开做比较好,因为我想的是后面如果做搜索,可以直接调用全部展开的按键就行了

直接上最终版本

    <button class="btn_1">
        全部展示
    </button>
    <button class="btn_2">
        全部隐藏
    </button>
            let item_li = document.getElementsByTagName('li')
            item_li[0].style.display = 'block'
            //按钮
            let btn_1 = document.querySelector('.btn_1')
            let btn_2 = document.querySelector('.btn_2')
            btn_1.onclick = function () {
                for (let i = 0; i < item_li.length; i++) {
                    item_li[i].style.display = 'block'
                    let ul_list = item_li[i].children
                    for (let i = 0; i < ul_list[0].children.length; i++) {
                        if (ul_list[0].children[i].children[0].children.length === 0) {
                            ul_list[0].children[i].style.color = 'red'
                        }
                    }
                }
            }
            btn_2.onclick = function () {
                for (let i = 0; i < item_li.length; i++) {
                    item_li[i].style.display = 'none'
                }
                item_li[0].style.display = 'block'
            }

最后如果li不能展开,就把li的字颜色改为红色便于判断

这里解释一下为什么要用双循环,因为li的下面第一个子元素的ul,所以得把这个li下面的ul的li集合单独拿出来,到最后一个ul的时候,下面的children为空数组

一件隐藏就保持第一行li显示

5.单独点击没一个树杈,能做到展开/隐藏下面的子元素

第一步,给所有里都批量绑定点击事件

第二步,点击显示/隐藏自己的子元素,依靠子元素的ul是否显示来进行判断什么时候需要显示/隐藏

第三步,直接上代码!

            for (let i = 0; i < item_li.length; i++) {
                //批量绑定点击事件
                item_li[i].onclick = function (e) {
                    let ul_list = item_li[i].children

                    //无展开列表提示背景
                    for (let i = 0; i < ul_list[0].children.length; i++) {
                        if (ul_list[0].children[i].children[0].children.length === 0) {
                            ul_list[0].children[i].style.color = 'red'
                        }
                    }

                    //如果是最后一个,则不隐藏
                    if (ul_list[0].children.length) {
                        if (ul_list[0].children[0].style.display == 'none') {
                            operateList(ul_list[0].children, 'block')
                        } else {
                            operateList(ul_list[0].children, 'none')

                        }
                    }

                    //防止点击穿透
                    event.stopPropagation();
                }
            }



             function operateList(arr, status) { // arr => 数组  status => 展开/隐藏
                for (let i = 0; i < arr.length; i++) {
                    arr[i].style.display = status
                }
             }

展开和隐藏只有两种状态,做了一个小封装看着些许顺眼

6.完成搜索框(核心仍为递归算法)

多的不说,先画图

    <div class="inp">
        <input type="text" id="inp">
        <button id="search">搜索</button>
    </div>
    <div class="title">
        <span id="sp_show" style="display: none;">不存在该用户</span>
    </div>

扁平化原数据,递归算法,启动!

    //扁平化
    function flat(data) {
        let arr = [...data]
        for (let i = 0; i < data.length; i++) {
            if (data[i].child.length) {
                arr.push(...flat(data[i].child))
            } else {
                arr.push(data[i])
            }
        }
        return arr
    }

因为后面要做锚点定位,再加一个事件点击的函数

    function triggerEvent(e, eName) {
        if (typeof e[eName] == 'function') {
            e[eName]();
        } else if (e.fireEvent) {
            e.fireEvent('on' + eName);
        } else if (document.createEvent) {
            var evt = document.createEvent("MouseEvents"); 
            evt.initEvent(eName, true, true); 
            e.dispatchEvent(evt);
        }
    }

然后就是获取输入框的值,利用扁平化过后的数据挨着查就行了,完整代码附上

            //锚点定位
            let input = document.getElementById('inp')
            let a_btn = document.getElementById('search')
            let sp_show = document.getElementById('sp_show')
            a_btn.onclick = function () {
                //判断id、名字、电话是否在列表中
                sp_show.style.display = 'none'
                let value = input.value
                let flag = false
                let id = null
                for (let i = 0; i < userList.length; i++) {
                    if (userList[i].id == value) {
                        id = userList[i].id
                        flag = true
                    }
                    if (userList[i].username == value) {
                        flag = true
                        id = userList[i].id
                    }
                    if (userList[i].mobile == value) {
                        flag = true
                        id = userList[i].id
                    }
                }
                if (!flag) {
                    sp_show.style.display = 'inline-block'
                } else {
                    triggerEvent(btn_1, 'click')
                    let a = document.createElement('a')
                    a.setAttribute('href', '#' + id)
                    a.setAttribute('style', "display:none")
                    triggerEvent(a, 'click')
                    for (let i = 0; i < item_li.length; i++) {
                        if (item_li[i].id == id) {
                            item_li[i].style.backgroundColor = '#000'
                            setTimeout(() => {
                                item_li[i].style.backgroundColor = '#eee'
                            }, 2000)
                        }
                    }
                }
            }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值