延迟加载 React 路由器路由加载

Lazy loaded React router routes loading anyway

我一直在尝试使用 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;

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

<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, fallback) => props => (
  <Suspense fallback={fallback}>
    <WrappedComponent {...props} />
  </Suspense>
);

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

export default withSuspense(Home, <div>Loading...<div>);

App.js

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

或在您的应用程序中装饰它们

const HomeWithSuspense = withSuspense(Home, <div>Loading...<div>);

...

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

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

万一有人遇到同样的问题,实际问题是某些组件中包含其他组件,这些组件没有默认导出,这就是它们没有延迟加载的原因。

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

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

感谢大家的帮助!

您尝试延迟加载的整棵树都不需要默认导入和导出。默认情况下,组件树及其唯一依赖项将被捆绑到惰性块中。

例如

Component.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

如果以上都有效请再试一次

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