您好,登錄后才能下訂單哦!
一、單頁應(yīng)用
使用傳統(tǒng)的多頁面實現(xiàn)方式,即每次頁面切換都是一次網(wǎng)頁刷新,每次頁面切換的時候都遵從以下的步驟:
(1)瀏覽器的地址欄發(fā)生變化指向新的URL,于是瀏覽器發(fā)起一個HTTP請求到服務(wù)器獲取頁面的完整HTML;
(2)瀏覽器獲取到HTML內(nèi)容后,解析HTML內(nèi)容;
(3)瀏覽器根據(jù)解析的HTML內(nèi)容確定還需要下載哪些其他資源,包括JavaScript和CSS資源;
(4)瀏覽器會根據(jù)HTML和其他資源渲染頁面內(nèi)容,然后等待用戶的其他操作。
上面的頁面存在很大的浪費,每個頁面切換都要重新刷新一遍頁面。而“單頁應(yīng)用”可以解決這樣的問題,“單頁應(yīng)用”只是局部更新,需要做到“單頁應(yīng)用”,需要達到以下的目標(biāo):
頁面內(nèi)容和URL保持一致包含兩個方面:第一個方面是指當(dāng)頁面切換的時候,URL會對應(yīng)改變,這通過瀏覽器的History API可以實現(xiàn)在不刷新網(wǎng)頁的情況下修改URL;另一方面,用戶在地址欄直接輸入某個正確的URL時,網(wǎng)頁上要顯示對應(yīng)的正確的內(nèi)容。
二、React-Router
React-Router庫可以幫我們創(chuàng)建React單頁應(yīng)用。每個URL都包含域名部分和路徑部分,例如對于URL http://localhost:3000/home 來說,路徑部分是home,因為應(yīng)用可能會被部署到任何一個域名上,所以決定一個URL顯示什么內(nèi)容的只有路徑部分,和域名以及端口沒有關(guān)系,根據(jù)路徑找到對應(yīng)應(yīng)用內(nèi)容的過程,也就是React-Router的重要功能-路由。
路由
React-Router庫提供了兩個組件來完成路由功能,一個是Router,一個是Route。Router在整個應(yīng)用中只需要一個實例,代表整個路由器,后者Route則代表每一個路徑對應(yīng)頁面的路由規(guī)則,一個應(yīng)用中應(yīng)該會有多個Route實例。
路由鏈接和嵌套
React-Router提供了一個名為Link的組件來支持路由鏈接,Link的作用是產(chǎn)生HTML的鏈接元素,但是對這個鏈接元素的點擊操作不會引起網(wǎng)頁跳轉(zhuǎn),而是被Link截獲操作,把目標(biāo)路徑發(fā)送給Router路由器,這樣Router就知道可以讓哪個Route下的組件顯示了。
建立Route組件之間的父子關(guān)系,這種方式,就是路由的嵌套。嵌套路由的好處就是每一層Route只決定到這一層的路徑,而不是整個路徑,所以非常靈活。
4.集成Redux
我們希望用Redux來管理應(yīng)用中的狀態(tài),所以要把Redux添加到應(yīng)用中取。
使用React-Redux庫的Provider組件,作為數(shù)據(jù)的提供者,Provider必須居于接受數(shù)據(jù)的React組件之上。而React-Redux庫Router組件,也有同樣的需要,有兩種解決辦法:
(1)讓Router成為Provider的子組件,例如在應(yīng)用的入口函數(shù)src/index.js中代碼修改成下面這樣:
Router可以是Provider的子組件,但是,不能夠讓Provider成為Router的子組件,因為Router的子組件只能是Route或者IndexRoute,否則運行時會報錯。
(2)使用Router的createElement屬性,通過給createElement傳遞一個函數(shù),可以定制創(chuàng)建每個Route的過程,這個函數(shù)第一個參數(shù)Component代表Route對應(yīng)的組件,第二個參數(shù)代表傳入組件的屬性參數(shù)。加上Provider的createElement可以這樣定義:
需要注意的是,Router會對每個Route的構(gòu)造都調(diào)用一遍createElement,也就是每個組件都創(chuàng)造一個Provider來提供數(shù)據(jù),這樣并不會產(chǎn)生性能問題。
Redux遵從一個重要的原則就是"唯一數(shù)據(jù)源",唯一數(shù)據(jù)源并不是說所有的數(shù)據(jù)都要存儲在一個地方,而是一個數(shù)據(jù)只存在一個地方,以路由為例,使用React-Redux,即使結(jié)合了Redux,當(dāng)前路由的信息也是存儲在瀏覽器的URL上,而不是像其他數(shù)據(jù)一樣存儲在Redux的Store上,這樣做并不違背“唯一數(shù)據(jù)源”的原則,獲取路由信息的唯一數(shù)據(jù)源就是當(dāng)前的URL。
不過,如果不是所有應(yīng)用狀態(tài)都存在Store上,就會有一個很大的缺點,當(dāng)利用Redux Devtools做調(diào)試時,無法重現(xiàn)網(wǎng)頁之間的切換,因為當(dāng)前路由作為應(yīng)用狀態(tài)根本沒有在Store狀態(tài)上體現(xiàn),而Redux Devtools操縱的只有狀態(tài)。為了克服這個缺點,我們可以利用react-router-redux庫來同步瀏覽器URL和Redux的狀態(tài)。顯然,這違反了“唯一數(shù)據(jù)源”的原則,但是只要兩者絕對保持同步,就不會帶來問題,否則,會出大問題。
三、代碼分片
借助React-Router,我們可以將需要多頁面的應(yīng)用構(gòu)建成“單頁應(yīng)用”,在服務(wù)器端對任何頁面請求都返回同樣一個HTML, 然后由一個打包好的JavaScript處理所有路由等應(yīng)用邏輯,在create-react-app創(chuàng)造的應(yīng)用中,由webpack產(chǎn)生的唯一打包JavaScript文件被命名為bundle.js。
對于小型的應(yīng)用,按照上面的方式就足夠了,但是,對于大型應(yīng)用,把所有應(yīng)用邏輯打包在一個bundle.js文件中,會影響用戶感知的性能。
在大型應(yīng)用中,因為功能很多,若把所有頁面的JavaScript打包到一個bundle.js中,那么用戶訪問任何一個網(wǎng)頁,都需要下載整個網(wǎng)站應(yīng)用的功能。雖然瀏覽器的緩存機制可以避免下次訪問時下載重復(fù)資源,但是給用戶的第一印象卻打了折扣。很明顯,當(dāng)應(yīng)用變得較大之后,就不能把所有JavaScript打包到一個bundle.js中。
為了提高性能,一個簡單有效的方法是對JavaScript分片打包,然后按需加載。也就是把JavaScript轉(zhuǎn)譯打包到多個文件中,每一個文件的大小可以被控制的比較小。這樣,訪問某個網(wǎng)頁的時候,只需要下載必須的JavaScript代碼就行,不用下載整個應(yīng)用的邏輯。
1.代碼分片的原則
最自然的方式就是根據(jù)頁面來劃分,如果有N個頁面,那就劃分出N個分片,現(xiàn)實中,各個網(wǎng)頁之間肯定有交叉的部分,比如A、B頁面都使用一個共同的組件X,而且對于React應(yīng)用來說,每個頁面都依賴于React庫,所有至少都有共同的React庫部分代碼,這些共同的代碼沒有必要在各個分片里重復(fù),需要抽取出來放在一個共享的打包文件中。
最終,理想情況下,當(dāng)一個網(wǎng)頁被加載時,它會獲取一個應(yīng)用本身的bundle.js文件,一個包含頁面間共同內(nèi)容的common.js文件,還有一個就是特定于這個頁面內(nèi)容的JavaScript文件。
為了實現(xiàn)代碼分片,可以使用webpack。webpack的工作方式是根據(jù)代碼中的import語句和require方法確定模塊之間的依賴關(guān)系,所以webpack可以發(fā)掘所有模塊文件的依賴圖表,從這個圖表中不難歸結(jié)出分片需要的信息。
如圖展示了webpack實現(xiàn)代碼分片的原理:
這樣,當(dāng)瀏覽器訪問頁面A時,只需要加載PageA.chunk.js、commont.js和bundle.js這三個文件,和頁面A無關(guān)的4號和5號文件不被加載,節(jié)省了代碼下載量。
當(dāng)然,提高網(wǎng)頁性能的另一個重要原則是減少http請求數(shù),雖然代碼分片減少了每個頁面的代碼下載量,卻也增加了引用的JavaScript資源數(shù),但是這只影響用戶訪問的第一個頁面。例如,用戶訪問的第一個頁面是A,下載PageA.chunk.js、commont.js和bundle.js這三個文件,隨后當(dāng)頁面切換到B時,因為瀏覽器的緩存作用,commont.js和bundle.js不用重新下載,所以新下載的文件只有PageB.chunk.js,當(dāng)應(yīng)用中頁面越多,這種優(yōu)化效果也明顯。
2.彈射和配置webpack
為了實現(xiàn)代碼分片,需要直接操作webpack的配置文件,不能再使用react-create-app產(chǎn)生的默認配置,首先我們要讓應(yīng)用從react-create-app制造的“安全艙“里彈射出來,在命令行執(zhí)行如下命令:
npm run eject
注意:彈射是不可逆的操作
執(zhí)行該命令后,應(yīng)用目錄下多了scripts和config兩個目錄,分別包含腳本和配置文件,同時應(yīng)用目錄下的packge.json文件發(fā)在了變化,包含了更多的內(nèi)容,至此彈射完成,但是功能和”彈射“之前別無二致,要改進功能還需要手工修改一些文件。
有兩個webpack配置,分別代表開發(fā)環(huán)境和產(chǎn)品環(huán)境。首先處理開發(fā)模式也就是npm start命令啟動的模式下的webpack配置。
打開config/webpack.config.dev.js找到給module.exports賦值的語句,在給module.exports賦值的對象中,找到output這個字段,在其中添加上關(guān)于chunkFilename的一行,然后找到plugins字段,這是一個數(shù)組,在里面添加一個元素增加Commons-ChunkPlugin, 代碼修改如下:
增加的output配置,是告訴webpack給每個分片都產(chǎn)生一個文件,文件名包括模塊名和后綴”chunk.js“,后綴名可以隨意起。
增加在plugins中的配置是告訴webpack把所有分片中共同的代碼提取出來,放在名為common.js的文件中。
生成的文件都帶上前綴路徑,是為了保持和原有的bundle.js文件所在目錄一致,也可以是任意一個位置。
上面的修改只針對開發(fā)模式,還要修改產(chǎn)品的webpack配置保持一致。
打開config/webpack.config.prod.js文件,在config/webpack.config.prod.js中的output已經(jīng)有了正確的chunkFileName,所以只需要在plugins中添加下面一行就行:
new webpack.optmize.CommonsChunkPlugins('common','static/js/common.[chunkhash:8].js'),產(chǎn)品環(huán)境多出了[chunkhash:8]的部分,這是為了讓瀏覽器緩存在文件內(nèi)容改變時失去效果。因為產(chǎn)品環(huán)境下打包的文件部署出去之后預(yù)期會被瀏覽器長期緩存,所以不能使用固定的文件名,否則后續(xù)部署的代碼更新無法被瀏覽為發(fā)現(xiàn)。所以每個文件名都會包含一個8位的根據(jù)文件內(nèi)容產(chǎn)生的hash結(jié)果,這樣當(dāng)文件內(nèi)容發(fā)生變化時,文件名也發(fā)生了變化,對應(yīng)文件的URL也就發(fā)生了變化,瀏覽器就會去下載最新的JavaScript打包資源。
3.動態(tài)加載分片
針對webpack的配置只是告訴webpack分片打包,但是webpack沒有”頁面“的概念,還是需要修改JavaScript代碼來確定怎樣按照頁面分片。
該實例中,我們希望Home、About、Notfound頁面每個都是按需加載的,這三個頁面都應(yīng)該有自己的分片,它們的內(nèi)容也就不包含在主體的bundle.js文件中。因為webpack的工作方式是根據(jù)代碼中的import和require函數(shù)來找到所有的文件模塊,所以,要讓這三個頁面不出現(xiàn)在bundle.js文件中,就不能再直接使用import命令來導(dǎo)入它們。
完成動態(tài)加載分片需要兩個方面:
(1)使用require.ensure讓webpack產(chǎn)生分片打包文件
在src/Stores.js中,注釋掉對Home、About、NotFound的import語句,并利用Route的getComponent屬性異步加載組件,代碼如下:
(2)使用React-Router的getComponent異步加載頁面分片文件
注意:webpack打包過程是對代碼靜態(tài)掃描的過程,即webpack工作的時候,縮寫的代碼并沒有運行,webpack看到import和require參數(shù)是字符串,那么webpack就能明確的知道文件模塊位置,如果是變量,那webpack無法在靜態(tài)掃描狀態(tài)下確定哪些文件應(yīng)該放在對應(yīng)分片中。
4.動態(tài)更新Store的reducer和狀態(tài)
當(dāng)實現(xiàn)動態(tài)加載分片后,功能模塊(React組件、reducer、Store)會被webpack分配到不同的分片文件中,包含在功能模塊中的reducer代碼也會也會被分配到不同的代碼文件。這樣,應(yīng)用的bundle.js文件中就沒有這些reducer函數(shù)的定義,每個應(yīng)用都有唯一的一個Redux Store,當(dāng)應(yīng)用啟動創(chuàng)建Store時,并不知道這個應(yīng)用中所有的reducer函數(shù)如何定義。所以,當(dāng)切換到某個頁面的時候,除了要加載對用的React組件,還要加載對應(yīng)的reducer,否則功能模塊無法正常工作。功能模塊依賴Store上的狀態(tài),所以當(dāng)頁面切換時,除了要更新reducer,Store上的狀態(tài)樹也可能需要做對應(yīng)改變,才能支持新加載的功能組件。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。