跳转到主要内容

标签(标签)

资源精选(342) Go开发(108) Go语言(103) Go(99) angular(82) LLM(78) 大语言模型(63) 人工智能(53) 前端开发(50) LangChain(43) golang(43) 机器学习(39) Go工程师(38) Go程序员(38) Go开发者(36) React(33) Go基础(29) Python(24) Vue(22) Web开发(20) Web技术(19) 精选资源(19) 深度学习(19) Java(18) ChatGTP(17) Cookie(16) android(16) 前端框架(13) JavaScript(13) Next.js(12) 安卓(11) 聊天机器人(10) typescript(10) 资料精选(10) NLP(10) 第三方Cookie(9) Redwoodjs(9) ChatGPT(9) LLMOps(9) Go语言中级开发(9) 自然语言处理(9) PostgreSQL(9) 区块链(9) mlops(9) 安全(9) 全栈开发(8) OpenAI(8) Linux(8) AI(8) GraphQL(8) iOS(8) 软件架构(7) RAG(7) Go语言高级开发(7) AWS(7) C++(7) 数据科学(7) whisper(6) Prisma(6) 隐私保护(6) JSON(6) DevOps(6) 数据可视化(6) wasm(6) 计算机视觉(6) 算法(6) Rust(6) 微服务(6) 隐私沙盒(5) FedCM(5) 智能体(5) 语音识别(5) Angular开发(5) 快速应用开发(5) 提示工程(5) Agent(5) LLaMA(5) 低代码开发(5) Go测试(5) gorm(5) REST API(5) kafka(5) 推荐系统(5) WebAssembly(5) GameDev(5) CMS(5) CSS(5) machine-learning(5) 机器人(5) 游戏开发(5) Blockchain(5) Web安全(5) Kotlin(5) 低代码平台(5) 机器学习资源(5) Go资源(5) Nodejs(5) PHP(5) Swift(5) devin(4) Blitz(4) javascript框架(4) Redwood(4) GDPR(4) 生成式人工智能(4) Angular16(4) Alpaca(4) 编程语言(4) SAML(4) JWT(4) JSON处理(4) Go并发(4) 移动开发(4) 移动应用(4) security(4) 隐私(4) spring-boot(4) 物联网(4) nextjs(4) 网络安全(4) API(4) Ruby(4) 信息安全(4) flutter(4) RAG架构(3) 专家智能体(3) Chrome(3) CHIPS(3) 3PC(3) SSE(3) 人工智能软件工程师(3) LLM Agent(3) Remix(3) Ubuntu(3) GPT4All(3) 软件开发(3) 问答系统(3) 开发工具(3) 最佳实践(3) RxJS(3) SSR(3) Node.js(3) Dolly(3) 移动应用开发(3) 低代码(3) IAM(3) Web框架(3) CORS(3) 基准测试(3) Go语言数据库开发(3) Oauth2(3) 并发(3) 主题(3) Theme(3) earth(3) nginx(3) 软件工程(3) azure(3) keycloak(3) 生产力工具(3) gpt3(3) 工作流(3) C(3) jupyter(3) 认证(3) prometheus(3) GAN(3) Spring(3) 逆向工程(3) 应用安全(3) Docker(3) Django(3) R(3) .NET(3) 大数据(3) Hacking(3) 渗透测试(3) C++资源(3) Mac(3) 微信小程序(3) Python资源(3) JHipster(3) 语言模型(2) 可穿戴设备(2) JDK(2) SQL(2) Apache(2) Hashicorp Vault(2) Spring Cloud Vault(2) Go语言Web开发(2) Go测试工程师(2) WebSocket(2) 容器化(2) AES(2) 加密(2) 输入验证(2) ORM(2) Fiber(2) Postgres(2) Gorilla Mux(2) Go数据库开发(2) 模块(2) 泛型(2) 指针(2) HTTP(2) PostgreSQL开发(2) Vault(2) K8s(2) Spring boot(2) R语言(2) 深度学习资源(2) 半监督学习(2) semi-supervised-learning(2) architecture(2) 普罗米修斯(2) 嵌入模型(2) productivity(2) 编码(2) Qt(2) 前端(2) Rust语言(2) NeRF(2) 神经辐射场(2) 元宇宙(2) CPP(2) 数据分析(2) spark(2) 流处理(2) Ionic(2) 人体姿势估计(2) human-pose-estimation(2) 视频处理(2) deep-learning(2) kotlin语言(2) kotlin开发(2) burp(2) Chatbot(2) npm(2) quantum(2) OCR(2) 游戏(2) game(2) 内容管理系统(2) MySQL(2) python-books(2) pentest(2) opengl(2) IDE(2) 漏洞赏金(2) Web(2) 知识图谱(2) PyTorch(2) 数据库(2) reverse-engineering(2) 数据工程(2) swift开发(2) rest(2) robotics(2) ios-animation(2) 知识蒸馏(2) 安卓开发(2) nestjs(2) solidity(2) 爬虫(2) 面试(2) 容器(2) C++精选(2) 人工智能资源(2) Machine Learning(2) 备忘单(2) 编程书籍(2) angular资源(2) 速查表(2) cheatsheets(2) SecOps(2) mlops资源(2) R资源(2) DDD(2) 架构设计模式(2) 量化(2) Hacking资源(2) 强化学习(2) flask(2) 设计(2) 性能(2) Sysadmin(2) 系统管理员(2) Java资源(2) 机器学习精选(2) android资源(2) android-UI(2) Mac资源(2) iOS资源(2) Vue资源(2) flutter资源(2) JavaScript精选(2) JavaScript资源(2) Rust开发(2) deeplearning(2) RAD(2)

你在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