【问题标题】:Lazy loaded React router routes loading anyway延迟加载的 React 路由器路由仍然加载
【发布时间】:2020-05-31 16:44:48
【问题描述】:

我一直在尝试使用 React.lazy 和 Suspense 在 React 中延迟加载路由。但是,无论当前路线如何,某些组件都会加载,确切地说是:Feed、Profile 和 Settings。

请注意,我实际上并不想延迟加载 MenuAppBar 和 SnackAlert 之类的组件,但如果我正常导入它们并删除它们的 Suspense,那么直接进行代码拆分甚至都不起作用,一切都加载了,整个应用程序只是一个块.

import {createMuiTheme, MuiThemeProvider} from "@material-ui/core";
import {yellow} from "@material-ui/core/colors";
import CssBaseline from "@material-ui/core/CssBaseline";
import axios from "axios";
import React, {lazy, Suspense, useEffect, useState} from "react";
import {BrowserRouter as Router, Route, Switch} from "react-router-dom";
import "./css/feed.css";
import "./css/style.css";

const Feed = lazy(() => import("./routes/Feed"));
const Profile = lazy(() => import("./routes/Profile"));
const Home = lazy(() => import("./routes/Home"));
const Settings = lazy(() => import("./routes/Settings"));
const NotFound = lazy(() => import("./routes/NotFound"));
const MenuAppBar = lazy(() => import("./components/MenuAppBar"));
const SnackAlert = lazy(() => import("./components/SnackAlert"));

const App: React.FC = () => {
    const [isLogged, setIsLogged] = useState(localStorage.getItem("token") ? true : false);

    const [user, setUser] = useState<User>(
        isLogged ? JSON.parse(localStorage.getItem("userInfo") as string) : {admin: false}
    );
    const [openError, setOpenError] = useState<boolean>(false);
    const [errorMsg, setErrorMsg] = useState<string>("");
    const [severity, setSeverity] = useState<Severity>(undefined);
    const [pwa, setPwa] = useState<any>(null);
    const [showBtn, setShowBtn] = useState<boolean>(false);
    const [isLight, setIsLight] = useState<boolean>(
        (JSON.parse(localStorage.getItem("theme") as string) as boolean) ? true : false
    );

    const theme: customTheme = {
        darkTheme: {
            palette: {
                type: "dark",
                primary: {
                    main: yellow[600]
                }
            }
        },
        lightTheme: {
            palette: {
                type: "light",
                primary: {
                    main: yellow[700]
                }
            }
        }
    };

    window.addEventListener("beforeinstallprompt", (event) => {
        event.preventDefault();
        setPwa(event);
        setShowBtn(true);
    });

    window.addEventListener("appinstalled", (e) => {
        setShowBtn(false);
        setErrorMsg("App installed!");
        setSeverity("success");
        setOpenError(true);
    });

    const handleClick = () => {
        if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
            setErrorMsg(`Please, open the share menu and select "Add to Home Screen"`);
            setSeverity("info");
            setOpenError(true);
        } else {
            if (pwa) {
                pwa.prompt();
                pwa.userChoice.then((choiceResult: {outcome: "accepted" | "refused"}) => {
                    if (choiceResult.outcome === "accepted") {
                        setErrorMsg("App downloading in the background..");
                        setSeverity("info");
                        setOpenError(true);
                    }
                    setPwa(null);
                });
            }
        }
    };

    useEffect(() => {
        const token: string | null = localStorage.getItem("token");
        let userInfo: User = JSON.parse(localStorage.getItem("userInfo") as string);
        if (userInfo && token && !userInfo.admin) {
            setUser(userInfo);
            setIsLogged(true);
        }
        if (isLogged) {
            axios
                .get("/api/auth/user", {
                    headers: {
                        "x-auth-token": `${token}`
                    }
                })
                .then((res) => {
                    if (!userInfo || !token) {
                        setUser(res.data as User);
                    }
                    localStorage.setItem(`userInfo`, JSON.stringify(res.data as User));
                    setIsLogged(true);
                })
                .catch((err) => {
                    if (err) {
                        setIsLogged(false);
                    }
                });
        } else {
            localStorage.removeItem("token");
            localStorage.removeItem("userInfo");
        }
    }, [isLogged]);

    return (
        <MuiThemeProvider theme={isLight ? createMuiTheme(theme.lightTheme) : createMuiTheme(theme.darkTheme)}>
            <CssBaseline />
            <Router>
                <Suspense fallback={<div></div>}>
                    <Route
                        path="/"
                        render={() => (
                            <>
                                <MenuAppBar
                                    isLogged={isLogged}
                                    setIsLogged={setIsLogged}
                                    user={user}
                                    setUser={setUser}
                                    isLight={isLight}
                                    setIsLight={setIsLight}
                                />
                                <SnackAlert severity={severity} errorMsg={errorMsg} setOpenError={setOpenError} openError={openError} />
                            </>
                        )}
                    />
                </Suspense>
                <Suspense fallback={<div></div>}>
                    <Switch>
                        <Route exact path="/" render={() => <Home />} />

                        <Route exact path="/profile/:id" render={() => <Profile />} />

                        <Route exact path="/feed" render={() => <Feed isLogged={isLogged} user={user} />} />

                        <Route
                            exact
                            path="/settings"
                            render={() => (
                                <Settings isLight={isLight} setIsLight={setIsLight} handleClick={handleClick} showBtn={showBtn} />
                            )}
                        />
                        <Route render={() => <NotFound />} />
                    </Switch>
                </Suspense>
            </Router>
        </MuiThemeProvider>
    );
};

export default App;

【问题讨论】:

    标签: reactjs lazy-loading react-router-dom


    【解决方案1】:

    您将整个Switch 包装在一个Suspense 中,因此所有组件将同时延迟加载。您可能只希望在第一次呈现特定路线时获取/加载每个。

    <Switch>
      <Route
        exact
        path="/"
        render={props => (
          <Suspense fallback={<div>Loading...<div>}>
            <Home {...props} />
          </Suspense>
        )}
      />
      <Route
        exact
        path="/profile/:id"
        render={props => (
          <Suspense fallback={<div>Loading...<div>}>
            <Profile {...props} />
          </Suspense>
        )}
      />
      <Route
        exact
        path="/feed"
        render={() => (
          <Suspense fallback={<div>Loading...<div>}>
            <Feed isLogged={isLogged} user={user} {...props} />
          </Suspense>
        )}
      />
      <Route
        exact
        path="/settings"
        render={() => (
          <Suspense fallback={<div>Loading...<div>}>
            <Settings isLight={isLight} setIsLight={setIsLight} handleClick={handleClick} showBtn={showBtn} {...props} />
          </Suspense>
        )}
      />
      <Route
        render={() => <NotFound />}
      />
    </Switch>
    

    这里有很多重复,所以把悬念分解成一个HOC是很实际的。

    const withSuspense = WrappedComponent => (
      <Suspense fallback={<div>Loading...<div>}>
        <WrappedComponent {...props} />
      </Suspense>
    );
    

    您可以装饰每个透视默认导出,即

    export default withSuspense(Home);
    

    App.js

    ...
    <Switch>
      <Route exact path="/" render={props => <Home {...props} />} />
    

    或在您的应用中装饰它们

    const HomeWithSuspense = withSuspense(Home);
    
    ...
    
    <Switch>
      <Route
        exact
        path="/"
        render={props => <HomeWithSuspense {...props} />}
      />
      ...
    </Switch>
    

    【讨论】:

    • 感谢您的回复非常感谢您花时间帮助我解决我的问题。我之前实际上已经尝试过这种方法,但我继续将 Suspense 重新应用到我想延迟加载的每个组件,但我仍然得到与我之前提到的相同 3 个组件相同的随机加载。组件是否正在加载,因为他们说 MenuAppBar 中也存在一个公共子组件,因此它们是由代理加载的?我希望我是有道理的。再次非常感谢您的回复。
    【解决方案2】:

    如果有人遇到同样的问题,实际的问题是某些组件内部还有其他组件,这些组件没有默认导出,这就是它们没有被延迟加载的原因。

    因此,如果您遇到同样的问题,您应该检查您尝试延迟加载的组件的导入树,并确保该树中的每个组件都默认导出。

    有关详细信息,请参阅named exports section in the react docs。

    感谢大家的帮助!

    【讨论】:

      【解决方案3】:

      这应该可行,我会查看其他问题,例如构建脚本或使用相同捆绑包的其他一些代码。 (例如你在cmets中提到的继承的东西)

      【讨论】:

      • 我设法修复了它。抱歉没有更新帖子。问题是在没有延迟加载的组件中,一些组件没有默认导出,React 的延迟加载不喜欢这样。如果有人遇到和我一样的问题,我会立即编辑主帖。感谢您的通知!
      【解决方案4】:

      您尝试延迟加载的整个树无需默认导入和导出。默认情况下,具有唯一依赖关系的组件树将被捆绑到惰性块中。

      例如。

      组件.js

      import { x, y } from z
      .....
      export default Component
      

      main.js

      const Component = React.lazy(() => import('Component.js')
      

      这里的 main.js 块不会包含来自 z 的任何代码或 来自 Component.js 的任何代码及其独特的依赖项

      https://webpack.js.org/guides/code-splitting/#dynamic-imports https://create-react-app.dev/docs/code-splitting/#appjs

      【讨论】:

        【解决方案5】:

        如果上述方法可行,请尝试一次

        import React, { Suspense, lazy } from "react";
        import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
        const Home = lazy(() => import("./components/Home"));
        const About = lazy(() => import("./components/About"));
        
        const App = () => (
           <Router>
             <Suspense fallback={<div>Loading...</div>}>
               <Routes>
                 <Route exact path='/' element={<Home/>}/>
                 <Route exact path='/about' element={<About/>}/>
                 </Routes>
             </Suspense>
           </Router>
         );
        
        export default App
        

        【讨论】:

          猜你喜欢
          • 2015-03-28
          • 1970-01-01
          • 2017-05-17
          • 1970-01-01
          • 2017-05-25
          • 2019-04-23
          • 1970-01-01
          • 1970-01-01
          • 2017-04-08
          相关资源
          最近更新 更多