React实现递归组件

简介: React实现递归组件

前言

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

9.png


链接

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

[{
        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;
}
相关文章
|
7月前
|
存储 前端开发 JavaScript
【前端开发】JS Vue React中的通用递归函数
【前端开发】JS Vue React中的通用递归函数
75 0
|
7月前
|
存储 JavaScript 前端开发
原生js vue react通用的递归函数
原生js vue react通用的递归函数
51 0
|
JavaScript 前端开发
原生js vue react通用的递归函数
原生js vue react通用的递归函数
75 0
|
前端开发
React 16.x折腾记 - (11) 结合Antd菜单控件(递归遍历组件)及常规优化
随着侧边栏的东东越来越多..本来不考虑的三级菜单,也需要考虑进去了; 一开始都是手动map去遍历对应的组件, 相关的的组id这些也是简单的判断下children就返回一个值; 有兴趣的瞧瞧
365 0
React 16.x折腾记 - (11) 结合Antd菜单控件(递归遍历组件)及常规优化
|
2月前
|
前端开发 JavaScript 开发者
深入理解React Hooks:提升前端开发效率的关键
【10月更文挑战第5天】深入理解React Hooks:提升前端开发效率的关键
|
1月前
|
前端开发 JavaScript 开发者
颠覆传统:React框架如何引领前端开发的革命性变革
【10月更文挑战第32天】本文以问答形式探讨了React框架的特性和应用。React是一款由Facebook推出的JavaScript库,以其虚拟DOM机制和组件化设计,成为构建高性能单页面应用的理想选择。文章介绍了如何开始一个React项目、组件化思想的体现、性能优化方法、表单处理及路由实现等内容,帮助开发者更好地理解和使用React。
73 9
|
2月前
|
前端开发
深入解析React Hooks:构建高效且可维护的前端应用
本文将带你走进React Hooks的世界,探索这一革新特性如何改变我们构建React组件的方式。通过分析Hooks的核心概念、使用方法和最佳实践,文章旨在帮助你充分利用Hooks来提高开发效率,编写更简洁、更可维护的前端代码。我们将通过实际代码示例,深入了解useState、useEffect等常用Hooks的内部工作原理,并探讨如何自定义Hooks以复用逻辑。
|
2月前
|
前端开发 JavaScript API
探索React Hooks:前端开发的革命性工具
【10月更文挑战第5天】探索React Hooks:前端开发的革命性工具
|
21天前
|
监控 前端开发 数据可视化
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
@icraft/player-react 是 iCraft Editor 推出的 React 组件库,旨在简化3D数字孪生场景的前端集成。它支持零配置快速接入、自定义插件、丰富的事件和方法、动画控制及实时数据接入,帮助开发者轻松实现3D场景与React项目的无缝融合。
82 8
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
|
24天前
|
前端开发 JavaScript 开发者
使用React和Redux构建高效的前端应用
使用React和Redux构建高效的前端应用
28 1