React Loadable 簡介
譯文地址:React Loadable 簡介
譯者:百度外賣FE - 李紹懿原文地址:Introducing React Loadable
背景
當你的項目足夠大時,把所有代碼打包到一個bundle中的啟動時間就會成為問題。這時就需要把app拆分為若干個bundle,然後根據需求動態載入它們。
一個大bundle VS 若干個小bundle:
那如何把一個bundle拆成幾個呢?這個問題其實已經被 Browserify 和 Webpack 這些工具解決得很好了。
但還要做的是在項目中找到合適的地方拆分bundle,然後非同步去載入。所以當項目中有東西在載入時的需要一種通信機制。
基於路由拆分 vs 基於組件拆分
通常的推薦做法就是把app根據路由進行拆分,然後非同步地去載入每一個。看上去這種做法對於大多數app已經足夠好,例如點擊一個連接然後載入一個新頁面,這種體驗還不賴。
但是,我們可以做得更好。
其實在大多數React的路由管理工具中,路由以組件的形式存在。它們並沒有什麼非常特別的地方。所以假設我們圍繞著組件優化而不是把責任推給路由會怎麼樣?這樣會給我們帶來什麼?
基於路由 VS 基於組件代碼拆分:
這會有很多結果。相比只是簡單根據路由拆分app,這樣做會有更多地方可以拆分。例如 Modals、tabs ,還有很多在用戶做相應操作之前隱藏內容的組件。
更別說那些需要推遲到高優先順序內容載入完成後才載入的內容了。一個在頁面底部而且依賴了一大串類庫的組件為什麼要和頁面頂部的內容同時載入呢?
你大可依然在路由只是簡單組件時拆分他們。對於你的app,不管黑貓白貓,捉到老鼠就是好貓。
但我們需要在讓組件層面拆分app像在路由層面拆分一樣簡單。簡單得只要改幾行代碼,其他的事就自動OK。
React Loadable簡介
React Loadable 是一個很小的庫,是我厭煩了你們總說這個很難做 之後寫出來的。
Loadable 是一個高階組件(創建組件的function)用來輕易地在組件層面拆分bundle。
我們試想一下有兩個組件,其中一個引入並渲染了另一個。
import AnotherComponent from ./another-component;nnclass MyComponent extends React.Component {n render() {n return <AnotherComponent/>;n }n}n
此時我們依賴了AnotherComponent並且通過import關鍵字同步引入。我們需要一種讓它非同步載入的方法。
使用ECMA中動態引用(一個T39提案,目前stage3 )的特性來修改我們的組件使之非同步載入AnotherComponent。
class MyComponent extends React.Component {n state = {n AnotherComponent: nulln };nn componentWillMount() {n import(./another-component).then(AnotherComponent => {n this.setState({ AnotherComponent });n });n }nn render() {n let {AnotherComponent} = this.state;n if (!AnotherComponent) {n return <div>Loading...</div>;n } else {n return <AnotherComponent/>;n };n }n}n
然而,這只是手動做法,並不適用大量其他各種各樣的場景。比如說當import()失敗的情況,以及服務端渲染的情況。
作為替代,你可以使用 Loadable 把問題抽象出來。Loadable的用法很簡單。你僅僅要做的就是把要載入的組件和當你載入組件時的「Loading」組件傳入一個方法中。
import Loadable from react-loadable;nnfunction MyLoadingComponent() {n return <div>Loading...</div>;n}nnconst LoadableAnotherComponent = Loadable(n () => import(./another-component),n MyLoadingComponentn);nnclass MyComponent extends React.Component {n render() {n return <LoadableAnotherComponent/>;n }n}n
但是如果組件載入失敗怎麼辦,我們還需要一個錯誤狀態提示。 為了讓你最大化控制要顯示的東西,錯誤提示只是簡單地作為LoadingComponent的一個prop傳入。
function MyLoadingComponent({ error }) {n if (error) {n return <div>Error!</div>;n } else {n return <div>Loading...</div>;n }n}n
基於import()的自動代碼拆分
import()的牛X之處在於 Webpack 2 可以自動拆分代碼,不論你在何時加入新代碼,都不用做其他額外的工作。
這意味著你在使用 React Loadable 時,你可以通過切換 import() 位置來輕易試驗代碼拆分點,以便讓你的app達到最佳性能。你可以在這查看示例工程。或者查看 Webpack 2 文檔(提示:一些相關文檔在require.ensure() 一節中)
避免組件載入閃爍
有時組件載入非常快(<200ms),這時載入中的樣式就會一閃而過。
有大量用戶研究表明,這樣會讓用戶感覺到比實際載入更長的等待時間。如果什麼都不顯示的話,用戶會感覺更快。所以Loading組件需要接收一個pastDelay prop。
這樣你的Loading組件只在載入時間比設定delay時間長時才會顯示。
export default function MyLoadingComponent({ error, pastDelay }) {n if (error) {n return <div>Error!</div>;n } else if (pastDelay) {n return <div>Loading...</div>;n } else {n return null;n }n}n
這個 delay 默認200ms,但你也可以給Loadable傳入第三個參數用來自定義這個值。
Loadable(n () => import(./another-component),n MyLoadingComponent,n 300n);n
預載入
作為優化,你也可以在組件渲染之前對它進行預載入。舉個例子,當你需要在點擊按鈕時載入一個新組建,可能需要用戶hover在按鈕上時就預載入它。
Loadable 創建的組件向外暴露了一個用於預載入的靜態方法,具體如下:
let LoadableMyComponent = Loadable(n () => import(./another-component),n MyLoadingComponent,n);nnclass MyComponent extends React.Component {n state = { showComponent: false };nn onClick = () => {n this.setState({ showComponent: true });n };nn onMouseOver = () => {n LoadableMyComponent.preload();n };nn render() {n return (n <div>n <button onClick={this.onClick} onMouseOver={this.onMouseOver}>n Show loadable componentn </button>n {this.state.showComponent && <LoadableMyComponent/>}n </div>n )n }n}n
服務端渲染
Loadable 通過控制最後一個參數同樣支持服務端渲染。服務端運行時,通過傳入要動態載入模塊的絕對路徑來允許 Loadable 同步 reqire() 模塊。
import path from path;nnconst LoadableAnotherComponent = Loadable(n () => import(./another-component),n MyLoadingComponent,n 200,n path.join(__dirname, ./another-component)n);n
這意味著你的「非同步載入」和「代碼拆分」模塊在服務端都是同步渲染。
此時在客戶端遇到的問題回來了。我們可以在服務端完整渲染應用,但在客戶端,我們同一時間只需要載入一個bunle。
設想一下如果我們能弄清楚服務端bundling進程中哪些bundle是我們所需的會怎樣?這樣我們就可以把這些bundle一下傳給客戶端並且帶上服務端渲染的確切狀態。
今天你其實離這個目標很近了。
因為我們在Loadable中掌握了所有server端依賴的路徑,我們可以添加一個新的flushServerSideRequires方法用來返回所有在服務端渲染的路徑。然後用webpack –json命令,我們就可以獲得一個匹配了對應文件的bundle(我的具體代碼)。
僅剩的問題就是如何在客戶端優美地使用 Webpack 。我會在發表完這篇文章後一直等待你們的消息。
這就是所有的「酷玩意」(原文:「cool shit」),我們可以把它們優雅地結合在一起。React Fiber(譯者註:React Fiber是React核心演算法的重寫)讓我們更智能指定哪些bundle需要直接傳輸而哪些需要推遲到更高優的工作完成後再載入。
最後,請您把這玩意安裝上再幫給我的repo給顆星
yarn add react-loadablen# ornnpm install --save react-loadablen
我也在twitter上
推薦閱讀:
※掌握這5大核心概念,你就理解了React
※React 模態框秘密和「輪子」漸進設計
※高性能 MobX 模式(part 3)- 用例教程
※React 許可證雖嚴苛,但不必過度 react