React实现递归组件
程序员文章站
2022-07-02 23:34:57
...
前言
今天来实现一个 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;
}
上一篇: 二到爆笑,逗的没边!
下一篇: 第一章 计算机组成