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;
}
相关文章
|
前端开发
React 16.x折腾记 - (11) 结合Antd菜单控件(递归遍历组件)及常规优化
随着侧边栏的东东越来越多..本来不考虑的三级菜单,也需要考虑进去了; 一开始都是手动map去遍历对应的组件, 相关的的组id这些也是简单的判断下children就返回一个值; 有兴趣的瞧瞧
312 0
|
18天前
|
前端开发 测试技术 开发工具
探索前端框架React Hooks的优势与应用
本文将深入探讨前端框架React Hooks的优势与应用。通过分析React Hooks的特性以及实际应用案例,帮助读者更好地理解和运用这一现代化的前端开发工具。
|
2月前
|
前端开发 JavaScript UED
使用React Hooks优化前端应用性能
本文将深入探讨如何使用React Hooks来优化前端应用的性能,重点介绍Hooks在状态管理、副作用处理和组件逻辑复用方面的应用。通过本文的指导,读者将了解到如何利用React Hooks提升前端应用的响应速度和用户体验。
|
11天前
|
开发框架 Dart 前端开发
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
【4月更文挑战第30天】对比 Flutter(Dart,强类型,Google支持,快速热重载,高性能渲染)与 React Native(JavaScript,庞大生态,热重载,依赖原生渲染),文章讨论了开发语言、生态系统、性能、开发体验、学习曲线、社区支持及项目选择因素。两者各有优势,选择取决于项目需求、团队技能和长期维护考虑。参考文献包括官方文档和性能比较文章。
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
|
11天前
|
前端开发 JavaScript 开发者
【专栏:HTML与CSS前端技术趋势篇】前端框架(React/Vue/Angular)与HTML/CSS的结合使用
【4月更文挑战第30天】前端框架React、Vue和Angular助力UI开发,通过组件化、状态管理和虚拟DOM提升效率。这些框架与HTML/CSS结合,使用模板语法、样式管理及组件化思想。未来趋势包括框架简化、Web组件标准采用和CSS在框架中角色的演变。开发者需紧跟技术发展,掌握新工具,提升开发效能。
|
12天前
|
开发框架 缓存 前端开发
|
15天前
|
前端开发 JavaScript Linux
relectron框架——打包前端vue3、react为pc端exe可执行程序
relectron框架——打包前端vue3、react为pc端exe可执行程序
28 1
|
16天前
|
前端开发 数据可视化 API
前端react 18.2整合ckeditor富文本编辑器——配置插件、自定义toolbar工具栏(二)
前端react 18.2整合ckeditor富文本编辑器——配置插件、自定义toolbar工具栏
30 0
前端react 18.2整合ckeditor富文本编辑器——配置插件、自定义toolbar工具栏(二)
|
16天前
|
前端开发 JavaScript CDN
前端react 18.2整合ckeditor富文本编辑器——配置插件、自定义toolbar工具栏(一)
前端react 18.2整合ckeditor富文本编辑器——配置插件、自定义toolbar工具栏
30 0
|
2月前
|
前端开发 JavaScript 安全
使用React、TypeScript和Ant Design构建现代化前端应用
使用React、TypeScript和Ant Design构建现代化前端应用
31 0