挨踢部落直播課堂第七期:如何使用React構建同構(isomorphic)應用


隨着前端的發展,爲了用戶體驗,H5愈來愈多的使用SPA架構,致使JS代碼愈來愈多,體積也變的龐大,這時傳統的ajax方式在首屏訪問時就變得慢了,並且ajax在seo方面有自然的弱勢,這時服務端渲染又回來了。咱們使用React搭配React Router等類庫來實現服務端渲染,讓首屏更快,seo更好。那麼,如何使用React構建同構(isomorphic)應用呢,咱們特此邀請到百安居前端架構師陳國興作直播分享。css

隨着前端的發展,爲了用戶體驗,H5愈來愈多的使用SPA架構,致使JS代碼愈來愈多,體積也變的龐大,這時傳統的ajax方式在首屏訪問時就變得慢了,並且ajax在seo方面有自然的弱勢,這時服務端渲染又回來了。咱們使用React搭配React Router等類庫來實現服務端渲染,讓首屏更快,seo更好。那麼,如何使用React構建同構(isomorphic)應用呢,咱們特此邀請到百安居前端架構師陳國興作直播分享。html

內容簡介
前端

1. 移動端爲何要用SPAvue

2. 傳統ajax方式和服務端渲染加載速度比較node

3. 服務端渲染技術詳解react

4. 同構方式的react代碼編寫一些須要注意的地方git

咱們會用到的react、react-router、redux這些庫,,代碼示例是以前的項目,react-router是2的版本,和最新的API可能會有一些差別。github

1、移動端爲何要用SPAweb

咱們先從爲何用SPA提及。這是由於移動互聯網的發展。頁面的跳轉若是使用傳統連接跳轉的方式,尤爲是在2.5G、3G時代,網速慢,不穩定,很容易點擊連接後,而後就看到一片白茫茫的頁面,運氣好,等一會到新的頁面,運氣很差,那就一直在白頁面上。因此須要SPA,至少在網絡很差的時候,還能夠看到頁面,這樣用戶的體驗會比較好。ajax

由於使用SPA的方式開發,必然致使客戶端JS是富客戶端的JS,那麼就帶來一個問題,代碼量多瞭如何管理,以及如何可維護。這就有了早期的BackBone,SpineJs等MVC框架,以及以後的MVP,MVVM等框架,把原來服務端的架構思想逐漸帶到前端。目前,以angular、vue、react最爲流行。

有人會問,爲何不選擇angular或者vue?用一句流行的話來講就是:angular(vue)你是個好人,但咱們不適合。固然,真正的緣由是React的組件化思想恰好和本身想要的匹配,是技術思想上的認同,而react出來時,vue那時還沒出來,angular真是又重又複雜。

2、傳統ajax問題和服務端渲染加載速度比較

咱們今天是講同構,同構首先是服務端渲染(×××),通常也稱爲首屏優化。我盜一張圖,來看傳統的頁面渲染流程。

1

最先的Web開發方式實際上是服務端渲染,可是後來你們以爲體驗很差,每一次都是要從新刷新頁面,這就有了ajax。最初,ajax並無問題。可是,移動時代來了,JS框架來了。JS變的愈來愈大了。

從上面的圖能夠看出,咱們要訪問一個頁面,首先是渲染一個沒有數據的空白頁面,而後加載資源,好比CSS,JS,一個打包壓縮好的JS文件甚至有好幾百K。等JS加載完了,這時才發起API請求,用戶還得繼續等,等到請求回來才能看到一個真正的頁面。因此這個時候,反而慢了,這時服務端渲染的方式又回來了。

我發個圖,極端狀況在慢速3G下的訪問狀況。

2

慢速的3G,沒有調用接口的狀況,到可正常訪問時,總時間在22.94s(不計圖片加載)。能夠看到,login頁面和main.css加載完是在3.96s。

若是是使用服務端渲染,是不須要js便可看到頁面的,也就是時間是這裏的login頁面和css加載完就能夠看到真正的頁面。而若是是傳統ajax方式,則是在22s多,二者有6倍左右的差距,若是再加上接口調用,咱們以前測試過,用戶看到首屏的的時間,有8-10倍左右的差距。

服務端渲染的首屏時間是:page+api request+css,page已經包含數據了。
客戶端的首屏時間是:page+css+js+api request。

除了客戶端須要加載一個很大的js文件外,API請求在服務端進行通常也是更快的。這裏簡單解釋一下首屏的概念:非首頁。從任何地方進來的那個頁面都是首屏。也就是說,作isomorphic,首先要保證沒有js的狀況,能夠直接從瀏覽器輸入任何一個地址進行訪問也是可訪問的。和早期的服務端渲染是同樣的。因此,這也是爲何SEO能更友好的緣由。

3、服務端渲染技術詳解

爲何要使用客戶端與服務端複用代碼的同構方式?維護性問題。客戶端是不安全的,因此服務端不能信任客戶端,須要作各類校驗,包括拉取數據後的ui渲染,這樣就須要先後端都要寫一次同樣邏輯的代碼。爲了開發效率、維護性等,因此須要複用。

這點上,nodejs有自然的優點。若是不考慮同構的話,光服務端渲染,其實很簡單,react提供了一個方法:renderToString()。只要把它取得的數據塞到模版文件裏就能夠了,好比nodejs的ejs文件。爲了代碼複用,咱們會考慮ui放服務端渲染,邏輯放服務端,API請求的代碼也共用一套,路由最好也是隻寫一次。react router它就支持服務端路由,而且它也爲服務端渲染提供了一些友好的API,好比Link。

接下來,咱們就把具體的代碼大概講一下。首先是路由定義。

3

這裏,history屬性在瀏覽器端與服務端是不同的,因此須要傳進來。瀏覽器端使用browserHistory:

import { browserHistory } from 'react-router'

4

服務器端使用createMemoryHistory:

import { RouterContext, createMemoryHistory, match } from 'react-router'

咱們把服務器端(nodejs)的路由配置所有貼出來,其實使用的是react-router提供的方法。

server.get('*', (req, res, next) => {   const history = createMemoryHistory()   const routes = createRoutes(history)   let store = configStore()    match({ routes, location: req.url }, (err, redirectLocation, renderProps) => {     if (err) {       res.status(500).send(err.message)     } else if (!renderProps) {       res.status(404).send('page not found')     } else {       getComponentFetch(renderProps, history, store).then(() => {         let reduxState = escape(JSON.stringify(store.getState()))         let html = ReactDOM.renderToString(           <Provider store={store}>             {<RouterContext {...renderProps} />}           </Provider>           )         res.render('home', { html, scriptSrcs, cs***c, reduxState })       })       .catch((err) => {         next(err)       })     }   }) })  function getComponentFetch (renderProps, history, store) {   let { query, params } = renderProps   let component = renderProps.components[renderProps.components.length - 1].WrappedComponent   let promise = component && component.fetchData ? component.fetchData({ query, params, store, history }) : Promise.resolve()   return promise }

路由匹配全部請求,當訪問時,根據路由配置,取得對應的react組件,由於要在服務端立刻調用API接口獲取數據,咱們會在容器組件放一個靜態方法:fetchData,調用這個方法來取得數據,而後放在一個變量傳給ejs模版文件。固然,咱們這時頁面已經渲染出數據了。這個reduxState變量的數據是作爲js加載完後 渲染時使用。

咱們看一下客戶端的代碼:

let reduxState = {} if (window.__STATE__) {   try {     reduxState = JSON.parse(unescape(__STATE__))   } catch (e) {   } } const store = configStore(reduxState)   ReactDOM.render((     <Provider store={store}>       {createRoutes(browserHistory)}     </Provider>     ), document.getElementById('container-root'))

window.__STATE__ 這個就是我從服務端傳過來的變量reduxState的值,用來初始化redux的store。

同時,若是爲了不首屏服務端請求一次數據,瀏覽器又再請求一次數據,咱們能夠把當前的container組件的displayName也從服務端傳回瀏覽器端,這樣在組件裏判斷有值,則不發起fetch請求,而是直接使用的是redux store的值。

fetchData的大概代碼我也貼一下:

static fetchData ({store}) {     let cityId = global.currentCityId     return store.dispatch(actions.getHomeData(cityId))   }

寫這個方法的目的也是爲了複用redux的邏輯,無論是action仍是store。這樣,咱們不須要掌握不少nodejs知識,只須要在server端配置一下路由,便可實現nodejs與瀏覽器端一套代碼複用。包括UI、邏輯、redux、路由。後續只須要正常寫組件,寫數據請求、邏輯等便可。

4、同構方式的react代碼編寫一些須要注意的地方

最後,講一下一些注意點。

一、在react的初次渲染的週期(constructor\componentWillMount\render),不要寫瀏覽器相關對象的代碼,好比window。另外:要注意componentDidMount是在瀏覽器端執行,在node端並不會執行。也不要在上面的幾個生命週期寫setState。

二、用戶首屏渲染後,在沒有加載js的狀況下,有可能立刻進行操做,好比連接跳轉或者表單提交,因此要假設沒有JS的狀況也能夠正常訪問。好比,表單提交使用form,連接使用href(react router的link)而不是onClick。這裏,react router的Link,當你js加載完後會自動把連接變成hash的形式。同時js加載完成後,就能夠把表單事件或者連接轉給js來處理了,後續的頁面就所有走ajax的方式跳轉。補充一下同構方式的渲染流程:用戶發起請求—>服務端接收到請求—> 匹配路由—>拉數據—>渲染界面—>拉JS代碼—>匹配瀏覽器路由—>走路由對應的組件的生命週期—>拉數據——>更新組件。因此,當js都down下來後,這時你的onClick事件纔是真正能夠生效的。

三、瀏覽器要訪問API地址,這個涉及到多個環境,我這裏爲了方便,是在個人node作代理中轉API請求的,這樣,瀏覽器端的請求的API地址只要是http://localhost 就能夠。nodejs端根據不一樣的環境取不一樣的API接口配置,並且這樣作有額外的好處,能夠繞過跨域,API後端服務不須要去配跨域這麼麻煩,瀏覽器的請求也能夠少一個option去校驗是否容許跨域訪問。

react同構,差很少就這些東西了。

如下問題是來自51CTO開發者社羣小夥伴們的提問和分享

Q:Java-workman-北京:若是隻用react+ajax的狀況效率會有變化嗎?不是一個新的應用,只是在原有基礎上使用react的dom去展現,和普通的ajax會有太大的出處嗎?

A:百安居-陳老師:這個效率就是以前說的,你要數據出來,必須得等你的JS文件下載完,而後發起請求,因此確定會比較慢。


Q:前端-Jouryjc-深圳:老師麻煩貼一下項目github。

A:百安居-陳老師:我本身有弄了一個startkit,並沒傳到github。


Q:數據-unicorn-北京:ant.design是目前最好的react框架嗎?

A:百安居-陳老師: ant.design不是react框架。只是UI。


Q:前端-秋香姐-深圳:node作代理中轉API請求 這個是怎麼作的啊?這個http-proxy是在服務端作的仍是在客戶的作的啊?

A:百安居-陳老師:用http-proxy。在nodejs端。

import httpProxy from 'http-proxy' const proxy = httpProxy.createProxyServer({   target: `${targetUrl}/api` }) server.use('/api', (req, res) => {   proxy.web(req, res) })

Q:前端-秋香姐-深圳:static fetchData 方法是啥時候怎麼調用的呀?

A:百安居-陳老師:

5

6


Q:數據-unicorn-北京:react UI框架您推薦那個呢?

A:百安居-陳老師: 這個要根據具體的場景,咱們通常都不用UI框架,都是根據具體設計來作。後臺的話,能夠考慮用Ant.Design,這個據說比較大,不適合面向終端用戶。


Q:前端-秋香姐-深圳:陳老師,作這個服務端渲染咱們是否是須要有一個node服務器呀?

A:百安居-陳老師:對的。


Q:前端-秋香姐-深圳:咱們對這個node服務器怎麼搭建配置呢?

A:百安居-陳老師:通常用node最好,由於語言同樣,複用性最高。我是用express,其實沒幾行代碼,基本都貼了。其實很簡單。就是配置一個路由,一個靜態的獲取數據方法供nodejs端調用。其餘的注意一下一些細節就行了。


Q:前端-秋香姐-深圳:對了,咱們作這個react同構,須要運維同窗幫咱們作些什麼配置嗎?仍是跟以前沒作react同構的服務器同樣嗎?

A:百安居-陳老師:須要跑一個nodejs服務。可能你以前的頁面是由Java之類的渲染,如今都交給nodejs就行了。Java之類的只須要提供API接口。


Q:呆丸-搬磚-烏龜:「Java之類的只須要提供API接口。」 這個意思是,前臺要本身搞個node服務器?

A:百安居-陳老師:nodejs服務器。同構,就是服務端、客戶端複用一套代碼。那麼既然有服務端了。


Q:Java-workman-北京:陳老師可否簡單的描述一下React的精髓或最優美的地方是什麼?

A:百安居-陳老師:我最佩服的是React那麼複雜的功能,它暴露出來的API卻很是簡潔,能夠說,只要一個render方法,就入門了,懂props、state就能寫大部分功能了。化繁爲簡的功力很是高深。

相關文章
相關標籤/搜索