你在React中使用过递归组件吗?我有。我与他们的第一次接触让我对从事前端项目有了全新的认识。因此,我认为写一篇关于在React中使用递归组件的真实世界示例的文章是一个好主意,以帮助人们更熟悉使用它们。
React中的递归组件:一个真实世界的例子
在本文中,我们将探讨React中递归组件的细节及其在现实应用程序中的使用。我们将在React中查看递归和非递归组件的示例,并评估它们的模块性、可读性和可维护性。之后,我们将构建一个真实世界的递归组件,一个类似于VS Code的嵌套文件资源管理器。
向前跳:
- React中的递归组件是什么?
- 递归与React中的循环有何不同?
- 为什么以及何时在React中使用递归组件
- 使用标准React组件构建嵌套的文件资源管理器
- 使用React中的递归组件构建嵌套的文件资源管理器
React中的递归组件是什么?
递归是在函数内部重复调用函数,直到满足基本条件(基本情况)。每次递归函数调用自己时,它都会接受一个新的参数。例如,下面的阶乘函数通过使用不同的参数重复调用自己来工作,直到满足基本条件:
function factorial(num) { if (num <= 1) { // base condition return 1; } return num * factorial(num - 1); // function calling itself with new input value. } console.log(factorial(6)); // 720
现在我们已经为理解递归组件奠定了基础,让我们在React中看看它们。正如您所知,React组件是函数。当React组件使用不同的道具重复渲染其内部,直到满足基本条件时,它被称为递归组件。
递归组件是唯一的,因为它可以调用自己,并有助于呈现深度嵌套的数据。这些组件并不局限于简单地在UI上呈现父数据;它们还渲染父母子女的数据,直到达到深度限制。在这些组件中,可以通过传递道具来设置儿童。
让我们看看React中递归组件的一个简单示例:
import React from "react"; const RecursiveComponent = ({children}) => { return ( <div> {children.length > 0 && <RecursiveComponent children={children} />} </div> ); }; export default RecursiveComponent;
如果你对这个概念在现实世界中的应用感到好奇,你可以在Reddit和类似于VS Code的文件浏览器中看到它们作为嵌套评论系统的作用。
现在您已经对递归组件有了基本的了解,我们将探索它们的用途,并构建一个真实的示例。
递归与React中的循环有何不同?
您可能想知道循环和递归之间的关键区别。在本节中,我们将了解这些差异。与循环相反,递归包括调用一个调用自身的函数,而循环则需要连续调用相同的代码,直到满足特定条件。
对于循环,我们必须首先定义我们的控制变量,然后才能在循环的任何迭代中使用它。例如,当我们创建循环以循环通过数组时,我们必须声明一个计数器变量,让i=0,以跟踪迭代次数。这允许我们在循环遍历数组时使用计数器变量。否则,我们将无法在需要时使用它,我们需要增加或减少这些控制变量以避免无限循环。
另一方面,在使用递归时,我们不必声明任何变量来执行递归操作。这是因为递归操作不依赖于变量,只需要一个基本条件就可以停止调用函数。
- 可返回性:对于循环,我们不能返回任何内容。然而,当我们使用递归时,我们可以从函数返回一个值
- 可读性和模块化:通过递归,代码变得更加可读和模块化。但是,使用循环会使代码变长
为什么以及何时在React中使用递归组件
在React中,组件是构建用户界面的主要构建块。它们非常棒,因为它们帮助我们全面思考我们的应用程序,并从更容易推理的较小代码块构建它。
你可能会想,“我们为什么要制作递归组件?”在React中使用递归组件的主要原因是它们使代码变得干燥、可读性更强、模块化。
解决了这个问题,让我们集中精力了解何时在React中使用递归组件。最常见的情况是当我们将数据嵌套到几个级别时。
假设我们有一个对象数组,每个对象都有一个子对象对应于另一个对象数组的键。该数组的每个对象部分都有一个子键,对应于另一个对象数组。同样,这些对象也可以包含更多的子数组。
此类数据的示例如下:
export const data = [ { isFolder: true, name: "public", children: [ { isFolder: false, name: "index.html", }, ], }, { isFolder: true, name: "src", children: [ { isFolder: true, name: "components", children: [ { isFolder: true, name: "home", children: [ { isFolder: false, name: "Home.js", }, ], }, ], }, { isFolder: false, name: "App.js", }, ], }, ];
正如您所看到的,上面的数据有很多嵌套的子数组。这些嵌套项目可以更深入,但我只将其深入到四个层次,以清楚地解释事情。
在这种情况下使用循环不是一个好的选择,因为它需要编写大量嵌套循环来循环遍历每一级数据。这会使代码变得更大、更难阅读。此外,如果我们不确定数据的深度,那么使用循环遍历所有嵌套数据是很有挑战性的。因此,在这种情况下,最好使用递归。
使用标准React组件构建嵌套的文件资源管理器
在本节中,我们将使用标准React组件构建一个嵌套的文件资源管理器应用程序。在本教程的下一节中,我们将使用递归组件构建相同的React应用程序。
首先,在src文件夹中创建一个新的React应用程序和一个数据文件夹。然后,在数据中创建data.js,并将“为什么以及何时在React中使用递归组件”部分的数据复制并粘贴到后面的部分。
现在,将App.js文件的代码替换为以下代码:
import React from "react"; import { data } from "./data/data"; const NonRecursiveComponent = ({ data }) => { return <div>Non Recursive Component</div>; }; const App = () => { return ( <div style={{ margin: "8px" }}> <NonRecursiveComponent data={data} /> </div> ); }; export default App;
我们在上面的代码中创建了一个NonRecursiveComponent,并在应用程序组件中进行了渲染。然后,我们将数据道具从应用程序传递给NonRecursiveComponent。
现在,让我们开始在UI上渲染数据。在NonRecursiveComponent中,将现有代码替换为以下代码:
const NonRecursiveComponent = ({ data }) => { return ( <div> {data.map((parent) => { return ( <div key={parent.name}> <span>{parent.name}</span> </div> ); })} </div> ); };
上面的代码将呈现所有父级(一级)数据,并在UI上显示这些数据:
React中的渲染递归组件
现在,让我们通过在第二个return语句中使用map方法来渲染子级。您的代码应该如下所示:
const NonRecursiveComponent = ({ data }) => { return ( <div> {data.map((parent) => { return ( <div key={parent.name}> <span>{parent.name}</span> {parent.children.map((child) => { // rendering children of the parent return ( <div key={child.name} style={{ paddingLeft: "20px" }}> <span>{child.name}</span> </div> ); })} </div> ); })} </div> ); };
从那里,您应该可以在UI上看到父级的所有子级:
React递归组件UI中的子级
让我们把孙子孙女们交出来。因此,要做到这一点,我们需要做与我们为儿童所做的相同的事情。您的代码应该如下所示:
const NonRecursiveComponent = ({ data }) => { return ( <div> {data.map((parent) => { // rendering parent data return ( <div key={parent.name}> <span>{parent.name}</span> {parent.children.map((child) => { // rendering children of the parent return ( <div key={child.name} style={{ paddingLeft: "20px" }}> <span>{child.name}</span> {child.children && // rendering grandchildren of the parent child.children.map((grandChild) => { return ( <div key={grandChild.name} style={{ paddingLeft: "20px" }}> <span>{grandChild.name}</span> </div> ); })} </div> ); })} </div> ); })} </div> ); };
您应该在UI上看到这样的内容:
React中递归组件中的孙子女
现在,我们最不需要做的就是在UI上渲染曾孙。我们需要做与我们为子孙后代所做的相同的事情。完成此操作后,您的代码应该如下所示:
const NonRecursiveComponent = ({ data }) => { return ( <div> {data.map((parent) => { // rendering parent data return ( <div key={parent.name}> <span>{parent.name}</span> {parent.children.map((child) => { // rendering children of the parent return ( <div key={child.name} style={{ paddingLeft: "20px" }}> <span>{child.name}</span> {child.children && // rendering grandchildren of the parent child.children.map((grandChild) => { return ( <div key={grandChild.name} style={{ paddingLeft: "20px" }} > <span>{grandChild.name}</span> {grandChild.children && // rendering great-grandchildren grandChild.children.map((greatGrandChild) => { return ( <div key={greatGrandChild.name} style={{ paddingLeft: "20px" }} > <span>{greatGrandChild.name}</span> </div> ); })} </div> ); })} </div> ); })} </div> ); })} </div> ); };
您的UI应该如下所示:
React递归组件文件
到目前为止,我们已经使用常规React组件构建了一个基本的嵌套文件资源管理器组件。但是,正如您所看到的,对于每个嵌套级别的数据,NonRecursiveComponent代码似乎都在重复自己。随着数据嵌套级别的提高,这些代码变得越来越长,使其更难理解和维护。
那么,我们该如何解决这个问题呢?在下一节中,我们将研究React中的递归组件如何提供帮助。
使用React中的递归组件构建嵌套的文件资源管理器
在本节中,我们将使用递归组件构建相同的嵌套文件资源管理器,并为嵌套文件和文件夹实现显示/隐藏功能。
让我们首先在App.js文件中创建一个新的RecursiveComponent。然后,将应用程序中呈现的NonRecursiveComponent替换为RecursiveComponent。
您的代码应该如下所示:
const RecursiveComponent = ({ data }) => { return ( <div style={{ paddingLeft: "20px" }}> Recursive Component </div> ); }; const App = () => { return ( <div style={{ margin: "8px" }}> <RecursiveComponent data={data} /> </div> ); };
现在,让我们在UI上渲染所有父数据:
const RecursiveComponent = ({ data }) => { return ( <div style={{ paddingLeft: "20px" }}> {data.map((parent) => { return ( <div key={parent.name}> <span>{parent.name}</span> </div> ); })} </div> ); };
接下来,让我们使用递归来渲染所有嵌套的文件和文件夹数据。要做到这一点,我们需要做两件事:首先,使用不同的数据道具从递归组件内部渲染递归组件。其次,我们需要一个基本条件来停止渲染递归组件。
在我们的例子中,基本条件是当子级的长度为零时,或者当它们不存在时,此时我们不会调用RecursiveComponent。
您的代码应该如下所示:
const RecursiveComponent = ({ data }) => { return ( <div style={{ paddingLeft: "20px" }}> {data.map((parent) => { return ( <div key={parent.name}> <span>{parent.name}</span> {/* Base Condition and Rendering recursive component from inside itself */} <div> {parent.children && <RecursiveComponent data={parent.children} />} </div> </div> ); })} </div> ); };
您的UI应该如下所示:
React递归组件的真实世界示例
😮惊喜通过几行代码,我们实现了相同的输出。这就是React中递归的魔力。
现在,让我们实现对嵌套文件和文件夹的显示/隐藏。在开始这样做之前,我们需要对代码进行一些更改。首先,我们需要有条件地使用数据中的isFolder键,将button标记用于文件夹的名称,将span标记用于文件的名称。我们这样做是因为我们只会将onClick事件添加到文件夹中,而不会添加到文件中,以显示或隐藏特定文件夹中的所有文件和文件夹。
更新后的代码应该如下所示:
const RecursiveComponent = ({ data }) => { return ( <div style={{ paddingLeft: "20px" }}> {data.map((parent) => { return ( <div key={parent.name}> {/* rendering folders */} {parent.isFolder && <button>{parent.name}</button>} {/* rendering files */} {!parent.isFolder && <span>{parent.name}</span>} <div> {parent.children && <RecursiveComponent data={parent.children} />} </div> </div> ); })} </div> ); };
现在,让我们通过在RecursiveComponent中使用useState Hook创建一个状态来实现show/hide,如下所示:
import { useState } from "react"; const RecursiveComponent = ({ data }) => { const [showNested, setShowNested] = useState({}); // ... // rest of the code // ... };
此showNested状态变量将存储所有打开的文件夹,如下所示:
{ public: true, // if the folder is opened, set it equal to true src: false // if the folder is not opened, set it equal to false }
现在,创建一个函数来处理显示/隐藏:
const递归组件=({data})=>{
const[showNested,setShowNested]=useState({});
//处理显示/隐藏功能
const RecursiveComponent = ({ data }) => { const [showNested, setShowNested] = useState({}); // handle show/hide functionality const toggleNested = (name) => { setShowNested({ ...showNested, [name]: !showNested[name] }); }; // ... // rest of the code // ... };
toggleNested函数接受文件夹的名称作为参数,然后使用setShowNested功能更新showNested中该文件夹的值。这会将其从false更改为true,反之亦然。
现在,让我们向按钮添加一个onClick事件来调用toggleNested。然后,当用户单击任何文件夹时,将文件夹的名称作为参数传递,如下所示:
const RecursiveComponent = ({ data }) => { // ... // Rest of the code return ( <div style={{ paddingLeft: "20px" }}> {data.map((parent) => { return ( <div key={parent.name}> {parent.isFolder && ( <button onClick={() => toggleNested(parent.name)}> {parent.name} </button> )} // ... // Rest of the code // ... </div> ); })} </div> );
最后一步是更新display CSS属性,我们将添加到包装递归组件内部的递归组件的div中。当用户选择文件夹时,我们将通过添加文件夹名称作为该对象的键来更新showNested对象。我们还将其值设置为true,并添加一个检查以查看showNested中是否存在文件夹名称。
然后,我们将显示器设置为块。否则,我们将设置为none以隐藏嵌套的文件和文件夹:
const RecursiveComponent = ({ data }) => { // ... // Rest of the code return ( <div style={{ paddingLeft: "20px" }}> {data.map((parent) => { return ( <div key={parent.name}> // ... // Rest of the code // ... // Updating the display property using the showNested state <div style={{ display: !showNested[parent.name] && "none" }}> {parent.children && <RecursiveComponent data={parent.children} />} </div> </div> ); })} </div> ); };
希望您能够切换所有目录。说完,这个博客就结束了!
结论
在本文中,您已经了解了React中的递归组件,如何构建它们,以及我们可能使用它们的原因。此外,我还用一个真实世界的例子展示了递归与循环的区别。希望你学到了一些对你的下一个React项目有用的新东西。
我希望你喜欢这篇文章,感谢你花时间阅读。如果你在阅读这篇文章时有任何问题或有进一步的问题,请在评论区告诉我。如果你喜欢我在这里做的事情,并想帮助我继续做下去,别忘了点击分享按钮。
几分钟内即可使用LogRocket的现代React错误跟踪:
参观 https://logrocket.com/signup/获取应用ID
通过npm或脚本标记安装LogRocket。LogRocket.int()必须调用客户端,而不是服务器端
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
(可选)安装插件,以便与您的堆栈进行更深层次的集成:
- Redux middleware
- NgRx middleware
- Vuex plugin
- 登录 发表评论