咱們都知道隨着單頁應用 bundle 的體積不斷增大,會形成首次加載時間過長(白屏時間過長),過程當中會加載了咱們首頁沒有必要看到的一些 頁面/組件 js文件,因此咱們須要對 bundle 文件進行拆分來進行按需加載(懶加載),這裏須要用到 webpack 支持的代碼拆分(code splitting)。
React.lazy 和 React.Suspense 是一對好兄弟,它兩就能夠完成一個優雅的懶加載方案,讓咱們來看看它們在 hook 中的使用吧。css
文章篇幅有一丟丟長,但請耐心品它 ~前端
pages/page1.tsxreact
import React from "react"; const Index: React.FC = () => { return <div>my name is page1,i am very lazy</div>; }; export default Index;
App.tsxwebpack
// ... const Page1 = React.lazy(() => import("./pages/page1")); // ... export default function App() { return ( <Router> <div id="app"> <Switch> <Route path="/page1" component={Page1} /> </Switch> </div> </Router> ); }
這裏咱們直接經過webpack支持的代碼拆分寫法 () => import("./pages/page1")
,可是報了個錯:web
上面報錯說的是須要與 React.Suspense 配合作 loading 或者佔位符,因此說它們是好兄弟,如影隨行 。那咱們來知足一下它們吧~網絡
App.tsxapp
const LazyPage1 = React.lazy(() => import("./pages/page1")); const Page1: React.FC = () => ( <React.Suspense fallback={<div>loading</div>}> <LazyPage1 /> </React.Suspense> );
注意:Suspense 組件須要包裹 React.lazy。異步
上面代碼咱們簡單用了 <div>loading</div>
做爲佔位符,效果以下:ide
爲了更加看得出效果,咱們能夠本身模擬網絡很卡的狀況下懶加載的效果,添加 slow 輔助方法測試
// ... const slow = (comp: any, delay: number = 1000): Promise<any> => { return new Promise((resolve) => { setTimeout(() => resolve(comp), delay); }); }; const LazyPage1 = React.lazy(() => slow(import("./pages/page1"), 3000)); // ...
注意:這只是個模擬,測試完畢記得不須要調用slow哦 ~
讓咱們再來看看模擬延遲3s的效果:
小總結:這樣就能夠實現一個不錯的懶加載了,咱們如今已經掌握了這兩個方法在hook中的基本用法了,聰明的你是否嗅到了一股能夠抽象封裝的味道 ~
咱們都知道,通常來講咱們會把組件分紅 頁面級組件 和 真正的高可複用的組件,
因此他們的 loading / 佔位符 是否也要作下區分,
我這裏想讓 異步頁面 加載的時候,用一個頁面絕對居中的loading做爲fallback,
讓 異步組件 加載的時候,用一個當前組件位置的loading做爲fallback。
HOCLazy/index.tsx
import React from "react"; import Loading from "./Loading"; import "./index.css"; interface IProps { isComponent?: boolean; content?: string; } const PH: React.FC<IProps> = ({ isComponent = false, content = "" }) => { return ( <> <div className={`ph ${!isComponent ? "page" : ""}`}> <div className="loading-wrapper"> <Loading /> <div>{content}</div> </div> </div> </> ); }; function HOCLazy<T = {}>(chunk: any): React.FC<IProps & T> { const WrappedComponent: React.FC = React.lazy(chunk); const Comp: React.FC<any> = ({ isComponent = false, content = "", ...props }) => { return ( <React.Suspense fallback={<PH isComponent={isComponent} content={content} />} > <WrappedComponent {...props} /> </React.Suspense> ); }; return Comp; } export default HOCLazy;
解釋:
泛型 <T>
來彌補這樣的「完整性」。App.tsx
// ... const Page1 = HOCLazy(() => slow(import("./pages/page1"), 3000)); // ...
pages/page1.tsx
import React from "react"; import HOCLazy from "../HOCLazy"; import slow from "../HOCLazy/slow"; import { IProps } from "../components/Comp1"; const Comp1 = HOCLazy<IProps>(() => slow(import("../components/Comp1"), 3000)); const Index: React.FC = () => { return ( <> <div>my name is page1,i am very lazy</div> <Comp1 isComponent content="Comp1組件加載中..." suffix="!!!" /> <Comp1 isComponent content="Comp1組件加載中..." suffix="!!" /> <Comp1 isComponent content="Comp1組件加載中..." suffix="!" /> </> ); }; export default Index;
若是咱們在頁面/組件每次 lazy loading 前都須要顯示「佔位符」,那麼可能會形成在網絡不錯的狀況下,頁面閃過「佔位符」,致使總體不美觀。
作lazy loading的目前是爲了讓頁面呈現更流暢順滑,這違背了咱們的初衷,
我想在網絡很好的狀況下,不讓用戶看到這個「佔位符」。
咱們須要改造一下
HOCLazy/index.tsx
// ... const PH: React.FC<IProps> = ({ isComponent = false, content = "" }) => { const [show, setShow] = useState<boolean>(false); const timeoutRef = useRef<number>(); useEffect(() => { const id = setTimeout(() => { setShow(true); // 延遲時間可本身拿捏 }, 500); timeoutRef.current = id; return () => { clearTimeout(timeoutRef.current); }; }, []); return ( <> {show ? ( <div className={`ph ${!isComponent ? "page" : ""}`}> <div className="loading-wrapper"> <Loading /> <div>{content}</div> </div> </div> ) : null} </> ); }; // ...
解釋:這裏經過延遲 500 毫秒顯示「佔位符」的方式,讓頁面在網絡良好的狀況下,不會出現「佔位符」一閃而過。
有了懶加載,咱們就能夠更靈活的利用 webpack 去分包,更細膩的去處理包的加載。如合併幾個頁面爲一個異步包:
const Page1 = HOCLazy(() => import(/* webpackChunkName: "page" */ "./pages/page1") ); const Page2 = HOCLazy(() => import(/* webpackChunkName: "page" */ "./pages/page2") );
執行build以後的包名,如:page.e6075659.chunk.js
SPA應用必不可少的就是懶加載,本篇提供了一個較爲合理的封裝思路。喜歡封裝的小夥伴,能夠繼續深究,更細節的去處理組件與loading/佔位符 樣式。
若是有更好的idea也能夠與我一塊兒討論,關注公衆號:前端精 ,讓咱們一塊兒進步吧 ~