欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

React实现递归组件

程序员文章站 2022-07-02 23:34:57
...

前言

今天来实现一个 React 的递归组件。具体的效果图如下:

React实现递归组件

假设后端返回的数据如下:

[{
        id: 1,
        parent_id: 0,
        name: '广东省',
        children: [{
                id: 2,
                parent_id: 1,
                name: '广州市',
                children: [{
                    id: 3,
                    parent_id: 2,
                    name: '番禺区'
                }, {
                    id: 4,
                    parent_id: 2,
                    name: '天河区'
                }],
            },
            {
                id: 7,
                parent_id: 1,
                name: '深圳市',
                children: [{
                        id: 8,
                        parent_id: 7,
                        name: '南山区'
                    },
                    {
                        id: 9,
                        parent_id: 7,
                        name: '宝安区'
                    },
                    {
                        id: 10,
                        parent_id: 7,
                        name: '龙岗区'
                    },
                    {
                        id: 11,
                        parent_id: 7,
                        name: '龙华区'
                    },
                ],
            },
        ],
    },
    {
        id: 56,
        parent_id: 0,
        name: '广西省',
        children: [{
            id: 57,
            parent_id: 56,
            name: '南宁市'
        }, ],
    },
]

分析

需求

  • 每层元素都有一个children属性,选中这项后展示这一项所有的子菜单,首次展示或者切换时默认展示第一项子菜单
  • 选中时高亮样式,默认为第一项高亮

实现思路

  • 每一层的显示逻辑应该是一样的,都是渲染这一层的列表即可
  • 对于子菜单的渲染逻辑也类似,如果当前项有 children 属性,递归调用即可
  • 高亮样式的逻辑:
    • 用一个路径数组记录好当前渲染的路径,如果某一项的 id 在数组里,则高亮
    • 切换父项时,重置数组中子项的 id ,这里可以用一个深度变量 dep 来控制

实现

渲染

  • 使用假数据来模拟,格式如上述
  • App.js中传入的dep是0,后面每渲染一层依次加1即可
  • 每一层初始渲染时默认将第一项作为高亮节点填入路径数组中,路径数组具体如何操作在下面
//App.js
import menuList from './mock/menu'
//......
this.state = {
    activePath: []
}
//......
componentDidMount() {
    let { id } = this.props.list[0]
    let { dep, activePath } = this.props
    if (!activePath[dep]) {
        this.props.changeActivePath(dep, id, this.props.list)
    }
}
render() {
    return (
        <Menu
            list={menuList}
            dep={0}
            //以下两个prop来控制高亮逻辑
            activePath={this.state.activePath}
            changeActivePath={this.changeActivePath.bind(this)}
        />
    )
}
  • 此处为渲染一层的逻辑,拿到这一层的数据后循环渲染一下即可
  • 标红的样式控制用上面说的路径数组来控制
//Menu.js
render() {
    let { list, dep } = this.props,
        renderList = list
    return (
        <div>
            <ul className="list">
                {renderList.map(item => {
                    return <li id={item.id}
                        onClick={this.clickItem.bind(this, item.id)}
                        className={this.props.activePath.includes(item.id) ? 'active' : ''}
                        key={item.id}>{item.name}
                    </li>
                })}
            </ul>
            {this.renderMore(list, dep)}
        </div>
    )
}

下面是渲染子节点的逻辑:

  • 先找出当前高亮的节点,要渲染它的子节点
  • 递归调用我们的 Menu 组件即可,注意层级加1
renderMore(list, dep) {
    let moreList = [], id
    for (let i = 0; i < list.length; i++) {
        //找出当前高亮的节点
        if (list[i].id == this.props.activePath[this.props.dep]) {
            moreList = list[i].children
            id = list[i].id
            break;
        }
    }
    if (Array.isArray(moreList) && moreList.length > 0) {
        return (
            <Menu
                list={moreList}
                dep={dep + 1}
                activePath={this.props.activePath}
                changeActivePath={this.props.changeActivePath.bind(this)}
            />
        )
    }
}

切换

  • 切换的逻辑十分简单,将点击的id传入即可
  • 下面具体来看路径数组的处理
clickItem(id) {
    this.props.changeActivePath(this.props.dep, id, this.props.list)
}

处理高亮逻辑

  • 如果是最后一层,则直接加入即可
  • 如果不是,则将当前层点击的节点填入数组,重新构建下面的子节点
  • 递归处理下子节点,默认是采用第一项作为高亮的节点
//App.js
changeActivePath(dep, id, list) {
    let activePath = this.state.activePath
    if (!activePath[dep] || dep == activePath.length - 1) {
        //最后一个 添进去即可
        activePath[dep] = id
    } else {
        //重新构建整个activePath数组
        activePath[dep] = id
        let cur = []
        for (let i = 0; i < list.length; i++) {
            let itemId = list[i].id
            if (itemId == id) {
                cur = list[i]
                break
            }
        }
        setPath(dep + 1, cur)
    }
    function setPath(dep, cur) {
        if (cur.children) {
            activePath[dep] = cur.children[0].id
            setPath(dep + 1, cur.children[0])
        }
    }
    this.setState({
        activePath
    })
}

完整代码

App.js

import React from 'react'
import Menu from './components/Menu'
import menuList from './mock/menu'
class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            activePath: []
        }
    }
    render() {
        return (
            <Menu
                list={menuList}
                dep={0}
                activePath={this.state.activePath}
                changeActivePath={this.changeActivePath.bind(this)}
            />
        )
    }
    changeActivePath(dep, id, list) {
        let activePath = this.state.activePath
        if (!activePath[dep] || dep == activePath.length - 1) {
            //最后一个 添进去即可
            activePath[dep] = id
        } else {
            //重新构建整个activePath数组
            activePath[dep] = id
            let cur = []
            for (let i = 0; i < list.length; i++) {
                let itemId = list[i].id
                if (itemId == id) {
                    cur = list[i]
                    break
                }
            }
            setPath(dep + 1, cur)
        }
        function setPath(dep, cur) {
            if (cur.children) {
                activePath[dep] = cur.children[0].id
                setPath(dep + 1, cur.children[0])
            }
        }
        this.setState({
            activePath
        })
    }
}
export default App

Menu.js

import React, { Component } from 'react'
import '../style/Menu.less'
class Menu extends Component {
    constructor(props) {
        super(props)
    }
    componentDidMount() {
        let { id } = this.props.list[0]
        let { dep, activePath } = this.props
        if (!activePath[dep]) {
            this.props.changeActivePath(dep, id, this.props.list)
        }
    }

    renderMore(list, dep) {
        let moreList = [], id
        for (let i = 0; i < list.length; i++) {
            if (list[i].id == this.props.activePath[this.props.dep]) {
                moreList = list[i].children
                id = list[i].id
                break;
            }
        }
        if (Array.isArray(moreList) && moreList.length > 0) {
            return (
                <Menu
                    list={moreList}
                    dep={dep + 1}
                    activePath={this.props.activePath}
                    changeActivePath={this.props.changeActivePath.bind(this)}
                />
            )
        }
    }
    clickItem(id) {
        this.props.changeActivePath(this.props.dep, id, this.props.list)
    }
    render() {
        let { list, dep } = this.props,
            renderList = list
        return (
            <div>
                <ul className="list">
                    {renderList.map(item => {
                        return <li id={item.id}
                            onClick={this.clickItem.bind(this, item.id)}
                            className={this.props.activePath.includes(item.id) ? 'active' : ''}
                            key={item.id}>{item.name}
                        </li>
                    })}
                </ul>
                {this.renderMore(list, dep)}
            </div>
        )
    }
}
export default Menu

Menu.less

ul,
li {
    list-style: none;
    margin: 0;
    padding: 0;
}

.list {
    display: flex;

    li {
        margin: 10px;
        cursor: pointer;
    }
}

.active {
    color: red;
}
相关标签: reactjs javascript