React 懒加载递归导入

3

我有一个需求,需要从动态文件夹中加载组件。例如,在组件内部我有以下文件夹:

components  
    -default
        -component-one
        -component-two
        -component-three
    -custom
        -component-three

假设componentFolder state设置为custom文件夹,那么应该从custom folder加载。如果在custom folder中找不到任何组件,则应从default文件夹加载。我的问题是,我们是否可以递归地导入?
 function App() {
 
const [componentFolder, setComponentFolder] = React.useState("default")

const Home = React.lazy(() => import("./components/" +componentFolder+ "/Home"));
  return (
    <div className="App">
      <Suspense fallback="laoding">
        <Home></Home>
      
      </Suspense>

    </div>
  );
}

下面的链接具有与我提出的要求相同的要求 如何在Reactjs中检查特定文件是否存在


@ajeet 谢谢提到这个问题...但其中一点提供了有关重新渲染的帮助 - scott
这是一个 CRA 项目吗? - lissettdm
注意:悬赏问题将在2小时后过期,因此将奖励给以下答案之一以表彰他们的努力。 - scott
5个回答

4
如果您使用 Webpack,则可以使用 require.context 动态加载模块: require.context
import React, { Suspense } from "react";

const load = async (path, file) => {
  const defaultPath = "default";
  const files = require.context("./components", true, /\.js$/);
  try {
    return files(`./${path}/${file}.js`);
  } catch (err) {
    return files(`./${defaultPath}/${file}.js`);
  }
};

export default function App() {
  const [componentFolder, setComponentFolder] = React.useState("default");
  const Home = React.lazy(() => load(componentFolder, "Home"));
  return (
    <div className="App">
      <Suspense fallback="loading">
        <Home />
      </Suspense>
    </div>
  );
}

@lissettdm,感谢您的回答。我没有使用ReactJS,而是使用Create React App。我不确定它是否使用Webpack。因为我是ReactJS的初学者。 - scott
你使用的是哪个模块打包工具?你能检查一下吗? - lissettdm
抱歉,我的上一条评论有错别字。我正在使用ReactJS,并使用Create React App https://create-react-app.dev/。如果满足我的要求,除了延迟加载之外,我也可以接受其他不同的解决方案。 - scott
CRA使用Webpack作为模块打包工具。您可以使用require.context动态加载模块,并将其与Lazy load结合使用。 - lissettdm

2

由于 lazy 返回一个 Promise,您可以使用其 catch 块,在原始模块未找到时返回另一个 lazy(Promise)。

示例:

import { lazy, Suspense, useState } from "react";

const rotate = {
  custom: "default",
  default: "custom",
};

function App() {
  const [folder, setFolder] = useState("custom");
  const [name, setName] = useState("component1");

  // Here: catch and return another lazy (promise)

  const Component = lazy(() =>
    import("./components/" + folder + "/" + name).catch(
      (err) => import("./components/" + rotate[folder] + "/" + name)
    )
  );

  return (
    <div>
      <Suspense fallback="laoding">
        <Component />
      </Suspense>
      <button onClick={() => setFolder(rotate[folder])}>toggle folder</button>
      <br />
      <button onClick={() => setName("component1")}>load component 1</button>
      <button onClick={() => setName("component2")}>load component 2</button>
      <button onClick={() => setName("component3")}>load component 3</button>
    </div>
  );
}

这里有一个演示
请注意,Component是在App组件内定义/创建的,在每次App重新渲染时都会被重新创建这将导致Component在App重新渲染时重置其状态

谢谢,但我正在寻找递归解决方案,因为可能存在多个父子层次结构。例如,默认->自定义->客户端特定,那么它将首先在客户端特定中进行检查,然后在自定义中进行检查,最后再到默认。是否有任何解决方案?无论如何都会尝试。 - scott
嗯,我以为你在例子中写错了“递归”,因为我没有看到任何递归路径。 - Ajeet Shah
@ajeeh.yes 我尝试了很多方法,但都没有成功。如果有除了懒加载之外的其他解决方案也可以。感谢您的努力。 - scott

1

根据其他回答和评论,我得出了以下结论:

https://codesandbox.io/s/so-react-lazy-recursive-import-2dqlp?file=/src/App.js

import React, { lazy, Suspense } from "react";

// test the code by removing the _ in front of file names

/*
components/
  comp  3? a root file will not trigger -> go to default
  
  /default
    comp  3! nice ^^ (but if it not exists will throw an error)

  /custom
    comp  2?
    /client
      comp  1?
        /omgStop
          heIsAlreadyDead (but works)
    /otherClient ...
*/

const recursiveImport = async (
  componentName,
  targetTree,
  defaultTree = "./components/default"
) => {
  console.count("paths tested");
  if (!targetTree) {
    return import(defaultTree + "/" + componentName);
  }

  return import("./components/" + targetTree + "/" + componentName).catch(
    () => {
      const newTreeArr = targetTree.split("/");
      newTreeArr.pop();
      const newTree = newTreeArr.join("/");
      return recursiveImport(componentName, newTree, defaultTree);
    }
  );
};

export default function App() {
  const targetTree = "custom/client1";
  const Component = lazy(() => recursiveImport("Test", targetTree));

  return (
    <div>
      <Suspense fallback="loading">{<Component />}</Suspense>
    </div>
  );
}

文件夹结构:

enter image description here

这解决了你所有的需求吗?

注意:悬赏问题将在2小时后过期,因此我将为以下答案中的一位付出努力的人提供悬赏。我会尽快尝试并告诉你结果。 - scott

0

简单而客观

const Recipe = React.lazy(() =>
  import(`docs/app/Recipes/${props.componentName}`)
  .catch(() => ({ default: () => <div>Not found</div> }))
);

-2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接