React中创建递归组件
您好,我是沧沧凉凉,是一名前端开发者,目前在掘金、知乎以及个人博客上同步发表一些学习前端时遇到的趣事和知识,欢迎关注。
这段时间我已经完全被React吸引了,在React中,一切皆有可能,从5月底正式在项目中使用React开始,到现在逐步开始看一些关于React底层的一些知识,随着对React的逐渐深入了解,越来越觉得React是那么美妙与神奇。
JSX是React的标志,JSX拥有JavaScript的完全编程能力,所以它也可以创造递归组件,递归组件就是自己调用自己的组件,用到递归组件的例子比如导航栏等等,一般是后端返回的数据层级不确定的情况下,我们要进行渲染,这个时候你就可以使用到递归组件,比如下面这个例子:
可以看出来,每一个按钮下的层级是不一样的,有的只有一层,有的有两层,有的有三层,这种层数不固定的组件,我们就需要使用到递归组件来进行处理。
其实在React的项目编写过程中,庞大的社区已经提供了非常多的UI组件供你选择,几乎已经能够满足你的大部分需求,但是不怕一万就怕万一,如果你遇到一个需求,刚好找不到你需要的这种组件,那么这时你就需要手写一个递归组件。
1. 数据格式
一般来讲,我们渲染这种递归组件时,数据一般都是来自后端,如果数据是写在前端的话,那就意味着数据不会发生改变,如果数据不会发生改变,那你写起来还不容易嘛,不用递归组件直接盒子堆叠也能够达成效果(当然导航栏的数据可能在前端)。
一般来自后端的数据格式为数组或者对象,往往对象里面有一个属性children
,而这个属性又对应着一个数组,而这个数组里面又是一些对象,然后对象又包含着children
属性,children
属性下又是数组,数组又是一些对象,对象下面又有children
属性…就相当于你搁这搁这搁这呢?这就组成了递归组件需要的数据。
那么上面图中使用到的数据为:
const testData = [
{
title: "0-0",
key: "0-0",
children: [
{
title: "0-0-0",
key: "0-0-0",
children: [
{ title: "0-0-0-0", key: "0-0-0-0" },
{ title: "0-0-0-1", key: "0-0-0-1" },
{ title: "0-0-0-2", key: "0-0-0-2" }
]
},
{
title: "0-0-1",
key: "0-0-1",
children: [
{ title: "0-0-1-0", key: "0-0-1-0" },
{ title: "0-0-1-1", key: "0-0-1-1" },
{ title: "0-0-1-2", key: "0-0-1-2" }
]
},
{
title: "0-0-2",
key: "0-0-2"
}
]
},
{
title: "0-1",
key: "0-1",
children: [
{ title: "0-1-0-0", key: "0-1-0-0" },
{ title: "0-1-0-1", key: "0-1-0-1" },
{ title: "0-1-0-2", key: "0-1-0-2" }
]
},
{
title: "0-2",
key: "0-2"
}
];
可以看到上面的数据中,如果没有children
则表示只有一层,即点击后不会有数据进行展示。
而有多层数据的,点击第一层的数据,就会展示第二层的数据,继续点击就可以继续展示下一层的数据。
2. 创建组件
首先,我们要了解一些ES11
中非常有用的新特性!可选链操作符:?:
。
const data = {
name: "张三",
age: 18,
sex: "男"
};
console.log(data.friend.name); // 报错:Uncaught TypeError: Cannot read property 'name' of undefined
通常我们在进行上面调用的情况下,在Vue2.x
中可能会出现白屏,而在React中会直接报错。
而这个时候我们就需要将console.log(data.friend.name);
写成console.log(data.friend && data.friend.name);
,如果层级很深或者需要判断的属性很多,你就需要写大量的判断代码。
而为了解决这个问题ES11
就发布了一个新特性可选链操作符:?:
。
console.log(data.friend && data.friend.name);
就等同于console.log(data.friend?.name);
,是不是写起来非常简单?
值得一提的是,在Vue2.x
中,如果你使用了最新的Babel那么你是可以在<script>
标签或者js
文件中使用这项特性,但是却无法在<template>
标签中使用这项特性,而在Vue3.x
中,是可以在<template>
中进行使用。
我们写递归组件和写递归函数一样要分为两个步骤:
- 编写基线渲染条件。
- 编写递归渲染条件。
比如要写一个上面演示的组件,那么你就需要思考哪些部分是递归渲染出来的,很明显,除了第一层外,都应该是递归进行渲染出来的,那么我们的重心就应该放在第一层组件应该如何编写上面。
这个时候又需要思考一个问题,如何渲染第一层组件,很显然,通过map
循环进行渲染。
理清了这个思路,我们就可以开始编写组件了:
<Space>
{/* 渲染首层 */}
{data?.map((item) => {
return (
<Tag.CheckableTag key={item.key}>{item.title}</Tag.CheckableTag>
);
})}
</Space>
历经了千辛万苦,首层终于渲染出来了,这个时候你应该可以看到下面的样子:
然后你就该思考如何实现点击选中的效果,这时你需要一个state
状态,来储存当前点击的是哪一项:
// 储存点击项的下标
const [select, setSelect] = useState(0);
<Space>
{/* 渲染首层 */}
{data?.map((item, index) => {
return (
<Tag.CheckableTag
onClick={() => {
// 将点击项的下标储存起来
setSelect(index);
}}
checked={select === index}
key={item.key}
>
{item.title}
</Tag.CheckableTag>
);
})}
</Space>
这个时候你就能得到下面的效果:
好了,点击效果也有了,你也将当前被点击的值记录下来了,那么就该来到最后一步了,递归渲染!最后一步简单的超出你的想象。
{/* 判断选中的项是否有children,如果有则递归渲染 */}
{data?.[select]?.children ? (
<Test data={data?.[select]?.children} />
) : null}
没错,就是这么一句代码,就实现了递归渲染,你就得到了本文开头的效果。
所有代码长这个样子:
import React, { useState } from "react";
import "antd/dist/antd.css";
import "./styles.css";
import { Space, Tag } from "antd";
const testData = [
{
title: "0-0",
key: "0-0",
children: [
{
title: "0-0-0",
key: "0-0-0",
children: [
{ title: "0-0-0-0", key: "0-0-0-0" },
{ title: "0-0-0-1", key: "0-0-0-1" },
{ title: "0-0-0-2", key: "0-0-0-2" }
]
},
{
title: "0-0-1",
key: "0-0-1",
children: [
{ title: "0-0-1-0", key: "0-0-1-0" },
{ title: "0-0-1-1", key: "0-0-1-1" },
{ title: "0-0-1-2", key: "0-0-1-2" }
]
},
{
title: "0-0-2",
key: "0-0-2"
}
]
},
{
title: "0-1",
key: "0-1",
children: [
{ title: "0-1-0-0", key: "0-1-0-0" },
{ title: "0-1-0-1", key: "0-1-0-1" },
{ title: "0-1-0-2", key: "0-1-0-2" }
]
},
{
title: "0-2",
key: "0-2"
}
];
function Test({ data }) {
// 储存点击项的下标
const [select, setSelect] = useState(0);
return (
<div>
<Space>
{/* 渲染首层 */}
{data?.map((item, index) => {
return (
<Tag.CheckableTag
onClick={() => {
// 将点击项的下标储存起来
setSelect(index);
}}
checked={select === index}
key={item.key}
>
{item.title}
</Tag.CheckableTag>
);
})}
</Space>
{/* 判断选中的项是否有children,如果有则递归渲染 */}
{data?.[select]?.children ? (
<Test data={data?.[select]?.children} />
) : null}
</div>
);
}
export default function App() {
return (
<div className="App">
<Test data={testData} />
</div>
);
}
到这里,我们仅仅用了20多行代码,就实现了一个看似复杂的组件。
3. 最后
递归组件在某些情况下非常实用,尤其是在你不知道要展示的数据究竟有多少层的情况下,所以递归组件也是一个进阶路上需要掌握的技能,掌握了这项技能,你才可以写出更加复杂的组件。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!