亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

詳解React+Koa實現服務端渲染(SSR)

2019-11-19 13:47:35
字體:
來源:轉載
供稿:網友

React是目前前端社區最流行的UI庫之一,它的基于組件化的開發方式極大地提升了前端開發體驗,React通過拆分一個大的應用至一個個小的組件,來使得我們的代碼更加的可被重用,以及獲得更好的可維護性,等等還有其他很多的優點...

通過React, 我們通常會開發一個單頁應用(SPA),單頁應用在瀏覽器端會比傳統的網頁有更好的用戶體驗,瀏覽器一般會拿到一個body為空的html,然后加載script指定的js, 當所有js加載完畢后,開始執行js, 最后再渲染到dom中, 在這個過程中,一般用戶只能等待,什么都做不了,如果用戶在一個高速的網絡中,高配置的設備中,以上先要加載所有的js然后再執行的過程可能不是什么大問題,但是有很多情況是我們的網速一般,設備也可能不是最好的,在這種情況下的單頁應用可能對用戶來說是個很差的用戶體驗,用戶可能還沒體驗到瀏覽器端SPA的好處時,就已經離開網站了,這樣的話你的網站做的再好也不會有太多的瀏覽量。

但是我們總不能回到以前的一個頁面一個頁面的傳統開發吧,現代化的UI庫都提供了服務端渲染(SSR)的功能,使得我們開發的SPA應用也能完美的運行在服務端,大大加快了首屏渲染的時間,這樣的話用戶既能更快的看到網頁的內容,與此同時,瀏覽器同時加載需要的js,加載完后把所有的dom事件,及各種交互添加到頁面中,最后還是以一個SPA的形式運行,這樣的話我們既提升了首屏渲染的時間,又能獲得SPA的客戶端用戶體驗,對于SEO也是個必須的功能。

OK,我們大致了解了SSR的必要性,下面我們就可以在一個React App中來實現服務端渲染的功能,BTW, 既然我們已經處在一個到處是async/await的環境中,這里的服務端我們使用koa2來實現我們的服務端渲染。

初始化一個普通的單頁應用SPA

首先我們先不管服務端渲染的東西,我們先創建一個基于React和React-Router的SPA,等我們把一個完整的SPA創建好后,再加入SSR的功能來最大化提升app的性能。

首先進入app入口 App.js:

import ReactDOM from 'react-dom';import { BrowserRouter as Router, Route } from 'react-router-dom';const Home = () => <div>Home</div>;const Hello = () => <div>Hello</div>;const App = () => { return (  <Router>   <Route exact path="/" component={Home} />   <Route exact path="/hello" component={Hello} />  </Router> )}ReactDOM.render(<App/>, document.getElementById('app'))

上面我們為路由/ 和 /hello創建了2個只是渲染一些文字到頁面的組件。但當我們的項目變得越來越大,組件越來越多,最終我們打包出來的js可能會變得很大,甚至變得不可控,所以呢我們第一步需要優化的是代碼拆分(code-splitting),幸運的是通過webpack dynamic import react-loadable,我們可以很容易做到這一點。

用React-Loadable來時間代碼拆分

使用之前,先安裝 react-loadable:

npm install react-loadable# oryarn add react-loadable

然后在你的 javascript中:

//...import Loadable from 'react-loadable';//...const AsyncHello = Loadable({ loading: <div>loading...</div>, //把你的Hello組件寫到單獨的文件中 //然后使用webpack的 dynamic import loader: () => import('./Hello'), })//然后在你的路由中使用loadable包裝過的組件:<Route exact path="/hello" component={AsyncHello} />

很簡單吧,我們只需要import react-loadable, 然后傳一些option進去就行了,其中的loading選項是當動態加載Hello組件所需的js時,渲染loading組件,給用戶一種加載中的感覺,體驗也會比什么都不加好。

好了,現在如果我們訪問首頁的話,只有Home組件依賴的js才會被加載,然后點擊某個鏈接進入hello頁面的話,會先渲染loading組件,并同時異步加載hello組件依賴的js,加載完后,替換掉loading來渲染hello組件。通過基于路由拆分代碼到不同的代碼塊,我們的SPA已經有了很大的優化,cheers🍻。更叼的是react-loadable同樣支持SSR,所以你可以在任意地方使用react-loadable,不管是運行在前端還是服務端,要讓react-loadable在服務端正常運行的話我們需要做一些額外的配置,本文后面會講到,先不急🏃。‍

到這里我們已經創建好一個基本的React SPA,加上代碼拆分,我們的app已經有了不錯的性能,但是我們還可以更加極致的優化app的性能,下面我們通過增加SSR的功能來進一步提升加載速度,順便解決一下SPA中的SEO問題🎉。

加入服務端渲染(SSR)功能

首先我們先搭建一個最簡單的koa web服務器:

npm install koa koa-router

然后在koa的入口文件app.js中:

const Koa = require('koa');const Router = require('koa-router');const app = new Koa();const router = new Router();router.get('*', async (ctx) => { ctx.body = `   <!DOCTYPE html>    <html lang="en">    <head>     <meta charset="UTF-8">     <title>React SSR</title>    </head>    <body>     <div id="app"></div>     <script type="text/javascript" src="/bundle.js"></script>    </body>   </html>  `;});app.use(router.routes());app.listen(3000, '0.0.0.0');

上面*路由代表任意的url進來我們都默認渲染這個html,包括html中打包出來的js,你也可以用一些服務端模板引擎(如:nunjucks)來直接渲染html文件,在webpack打包時通過html-webpack-plugin來自動插入打包出來的js/css資源路徑。

OK, 我們的簡易koa server好了,接下來我們開始編寫React SSR的入口文件AppSSR.js,這里我們需要使用StaticRouter來代替之前的BrowserRouter,因為在服務端,路由是靜態的,用BrowserRouter的話是不起作用的,后面還會做一些配置來使得react-loadable運行在服務端。

提示: 你可以把整個node端的代碼用ES6/JSX風格編寫,而不是部分commonjs,部分JSX, 但這樣的話你需要用webpack把整個服務端的代碼編譯成commonjs風格,才能使得它運行在node環境中,這里的話我們把React SSR的代碼單獨抽出去,然后在普通的node代碼里去require它。因為可能在一個現有的項目中,之前都是commonjs的風格,把以前的node代碼一次性轉成ES6的話成本有點大,但是可以后期一步步的再遷移過去

OK, 現在在你的 AppSRR.js中:

import React from 'react';//使用靜態 static routerimport { StaticRouter } from 'react-router-dom';import ReactDOMServer from 'react-dom/server';import Loadable from 'react-loadable';//下面這個是需要讓react-loadable在服務端可運行需要的,下面會講到import { getBundles } from 'react-loadable/webpack';import stats from '../build/react-loadable.json';//這里吧react-router的路由設置抽出去,使得在瀏覽器跟服務端可以共用//下面也會講到...import AppRoutes from 'src/AppRoutes';//這里我們創建一個簡單的class,暴露一些方法出去,然后在koa路由里去調用來實現服務端渲染class SSR { //koa 路由里會調用這個方法 render(url, data) {  let modules = [];  const context = {};  const html = ReactDOMServer.renderToString(   <Loadable.Capture report={moduleName => modules.push(moduleName)}>    <StaticRouter location={url} context={context}>     <AppRoutes initialData={data} />    </StaticRouter>   </Loadable.Capture>  );  //獲取服務端已經渲染好的組件數組  let bundles = getBundles(stats, modules);  return {   html,   scripts: this.generateBundleScripts(bundles),  }; } //把SSR過的組件都轉成script標簽扔到html里 generateBundleScripts(bundles) {  return bundles.filter(bundle => bundle.file.endsWith('.js')).map(bundle => {   return `<script type="text/javascript" src="${bundle.file}"></script>/n`;  }); } static preloadAll() {  return Loadable.preloadAll(); }}export default SSR;

當編譯這個文件的時候,在webpack配置里使用target: "node" externals,并且在你的打包前端app的webpack配置中,需要加入react-loadable的插件,app的打包需要在ssr打包之前運行,不然拿不到react-loadable需要的各組件信息,先來看app的打包:

//webpack.config.dev.js, app bundleconst ReactLoadablePlugin = require('react-loadable/webpack') .ReactLoadablePlugin;module.exports = { //... plugins: [  //...  new ReactLoadablePlugin({ filename: './build/react-loadable.json', }), ]}

在.babelrc中加入loadable plugin:

{ "plugins": [   "syntax-dynamic-import",   "react-loadable/babel",   ["import-inspector", {    "serverSideRequirePath": true   }]  ]}

上面的配置會讓react-loadable知道哪些組件最終在服務端被渲染了,然后直接插入到html script標簽中,并在前端初始化時把SSR過的組件考慮在內,避免重復加載,下面是SSR的打包:

//webpack.ssr.jsconst nodeExternals = require('webpack-node-externals');module.exports = { //... target: 'node', output: {  path: 'build/node',  filename: 'ssr.js',  libraryExport: 'default',  libraryTarget: 'commonjs2', }, //避免把node_modules里的庫都打包進去,此ssr js會直接運行在node端, //所以不需要打包進最終的文件中,運行時會自動從node_modules里加載 externals: [nodeExternals()], //...}

然后在koa app.js, require它,并且調用SSR的方法:

//...koa app.js//build出來的ssr.jsconst SSR = require('./build/node/ssr');//preload all components on server side, 服務端沒有動態加載各個組件,提前先加載好SSR.preloadAll();//實例化一個SSR對象const s = new SSR();router.get('*', async (ctx) => { //根據路由,渲染不同的頁面組件 const rendered = s.render(ctx.url);  const html = `  <!DOCTYPE html>   <html lang="en">   <head>    <meta charset="UTF-8">   </head>   <body>    <div id="app">${rendered.html}</div>    <script type="text/javascript" src="/runtime.js"></script>    ${rendered.scripts.join()}    <script type="text/javascript" src="/app.js"></script>   </body>  </html> `; ctx.body = html;});//...

以上是個簡單的實現React SSR到koa web server, 為了使react-loadable知道哪些組件在服務端渲染了,rendered里面的scripts數組里面包含了SSR過的組件組成的各個script標簽,里面調用了SSR#generateBundleScripts()方法,在插入時需要確保這些script標簽在runtime.js之后((通過 CommonsChunkPlugin 來抽出來)),并且在app bundle之前(也就是初始化的時候應該已經知道之前的哪些組件已經渲染過了)。更多react-loadable服務端支持,參考這里.

上面我們還把react-router的路由都單獨抽出去了,使得它可以運行在瀏覽器跟服務端,以下是AppRoutes組件:

//AppRoutes.jsimport Loadable from 'react-loadable';//...const AsyncHello = Loadable({ loading: <div>loading...</div>, loader: () => import('./Hello'), })function AppRoutes(props) { <Switch>  <Route exact path="/hello" component={AsyncHello} />  <Route path="/" component={Home} /> </Switch> }export default AppRoutes//然后在 App.js 入口中import AppRoutes from './AppRoutes';// ...export default () => { return (  <Router>   <AppRoutes/>  </Router> )}

服務端渲染的初始狀態

目前為止,我們已經創建了一個React SPA,并且能在瀏覽器端跟服務端共同運行🍺,社區稱之為universal app 或者 isomophic app。但是我們現在的app還有一個遺留問題,一般來說我們app的數據或者狀態都需要通過遠端的api來異步獲取,拿到數據后我們才能開始渲染組件,服務端SSR也是一樣,我們要動態的獲取初始數據,然后才能扔給React去做SSR,然后在瀏覽器端我們還要初始化就能同步獲取這些SSR時的初始化數據,避免瀏覽器端初始化時又重新獲取了一遍。

下面我們簡單從github獲取一些項目的信息作為頁面初始化的數據, 在koa的app.js中:

//...const fetch = require('isomorphic-fetch');router.get('*', async (ctx) => { //fetch branch info from github const api = 'https://api.github.com/repos/jasonboy/wechat-jssdk/branches'; const data = await fetch(api).then(res => res.json());  //傳入初始化數據 const rendered = s.render(ctx.url, data);  const html = `  <!DOCTYPE html>   <html lang="en">   <head>    <meta charset="UTF-8">   </head>   <body>    <div id="app">${rendered.html}</div>        <script type="text/javascript">window.__INITIAL_DATA__ = ${JSON.stringify(data)}</script>        <script type="text/javascript" src="/runtime.js"></script>    ${rendered.scripts.join()}    <script type="text/javascript" src="/app.js"></script>   </body>  </html> `; ctx.body = html;});

然后在你的Hello組件中,你需要checkwindow里面(或者在App入口中統一判斷,然后通過props傳到子組件中)是否存在window.__INITIAL_DATA__,有的話直接用來當做初始數據,沒有的話我們在componentDidMount生命周期函數中再去來數據:

export default class Hello extends React.Component { constructor(props) {  super(props);  this.state = {   //這里直接判斷window,如果是父組件傳入的話,通過props判斷   github: window.__INITIAL_DATA__ || [],  }; }  componentDidMount() {  //判斷沒有數據的話,再去請求數據  //請求數據的方法也可以抽出去,以讓瀏覽器及服務端能統一調用,避免重復寫  if (this.state.github.length <= 0) {   fetch('https://api.github.com/repos/jasonboy/wechat-jssdk/branches')    .then(res => res.json())    .then(data => {     this.setState({ github: data });    });  } }  render() {  return (   <div>    <ul>     {this.state.github.map(b => {      return <li key={b.name}>{b.name}</li>;     })}    </ul>   </div>  ); }}

好了,現在如果頁面被服務端渲染過的話,瀏覽器會拿到所有渲染過的html, 包括初始化數據,然后通過這些SSR的內容配合加載的js,再組成一個完整的SPA,就像一個普通的SPA一樣,但是我們得到了更好的性能,更好的SEO😎。

React-v16 更新

在React的最新版v16中,SSR的API做了很多的優化,并且提供了新的基于流的API來更好的提升性能,通過streaming api, 服務端可以邊渲染邊把前面渲染好的html發到瀏覽器,瀏覽器端也可以提前開始渲染頁面而不是等服務端所有組件都渲染完成后才能開始瀏覽器端的初始化,提升了性能也降低了服務端資源的消耗。還有一個在瀏覽器端需要注意的是需要使用ReactDOM.hydrate()來代替之前的ReactDOM.render(),更多的更新參考medium文章whats-new-with-server-side-rendering-in-react-16.

💖要查看完整的demo, 參考koa-web-kit, koa-web-kit是一個現代化的基于React/Koa的全棧開發框架,包括React SSR支持,可以直接用來測試服務端渲染的功能😀

結論

好了,以上就是React-SSR + Koa的簡單實踐,通過SSR,我們既提升了性能,又很好的滿足了SEO的要求,Best of the Both Worlds🍺。

English Version

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲经典中文字幕| 91国在线精品国内播放| 日本一区二区不卡| 久久国产色av| 久久国产精品久久精品| 国产亚洲精品美女| 97国产成人精品视频| 91精品国产自产在线| 欧美成人亚洲成人日韩成人| 国产精品久久久久77777| 久久久女女女女999久久| 最近2019好看的中文字幕免费| 精品视频久久久久久久| 亚洲精品久久久久中文字幕二区| 欧美在线性视频| 精品国产一区二区三区在线观看| 亚洲国产精品va| 26uuu亚洲国产精品| 亚洲精品白浆高清久久久久久| 欧美亚洲视频在线看网址| 欧美激情视频在线观看| 日韩黄色在线免费观看| 国产精品入口免费视频一| 51ⅴ精品国产91久久久久久| 亚洲男人天堂视频| 欧美日韩精品在线视频| 亚洲欧洲成视频免费观看| 欧美人在线视频| 欧美日韩在线视频首页| 国产精品夫妻激情| 亚洲国产成人av在线| 国产成人一区二| 亚洲天堂av在线免费观看| 国产91|九色| 欧亚精品中文字幕| 神马国产精品影院av| 国产成人精品久久久| 欧美乱妇高清无乱码| 国产精品久久久久久久久免费| 蜜臀久久99精品久久久久久宅男| 亚洲国产精品成人一区二区| 国产精品亚洲自拍| 欧美日韩国产激情| 97精品国产97久久久久久免费| 1769国内精品视频在线播放| 日韩av最新在线观看| 久久99精品视频一区97| 夜夜嗨av色一区二区不卡| 国产精品视频999| 国产精品91视频| 都市激情亚洲色图| 日韩精品免费在线视频| 国产精品十八以下禁看| 中文字幕日韩专区| www.日韩欧美| 97成人超碰免| 国产主播喷水一区二区| 亚洲新中文字幕| 日韩亚洲一区二区| 国产精品日韩欧美综合| 亚洲曰本av电影| 久久天天躁狠狠躁夜夜av| 亚洲男人的天堂在线播放| 97在线视频精品| 欧美日韩国产丝袜美女| 精品日韩美女的视频高清| 成人97在线观看视频| 91免费版网站入口| 日韩综合中文字幕| 日韩中文字幕免费看| 国产成人精品在线播放| 亚洲在线视频观看| 欧美大尺度电影在线观看| 国内精品在线一区| 久久最新资源网| 亚洲欧美日韩爽爽影院| 色综合久久88色综合天天看泰| 欧美电影在线播放| 亚洲肉体裸体xxxx137| 精品国偷自产在线视频99| 国产成人福利视频| 亚洲美女精品成人在线视频| 亚洲天堂久久av| 成人久久18免费网站图片| 国产亚洲美女久久| 欧美黑人性生活视频| 中文字幕国产精品久久| 麻豆国产精品va在线观看不卡| 久久91精品国产91久久跳| 国产成人高清激情视频在线观看| 欧美色视频日本高清在线观看| 久久99视频精品| 欧美精品免费看| 国产精品男人爽免费视频1| 亚洲黄色在线看| 欧美极度另类性三渗透| 久久影视电视剧免费网站清宫辞电视| 亚洲毛茸茸少妇高潮呻吟| 国产精品视频男人的天堂| 亚洲精品国产欧美| 国产精品久久久久久久一区探花| 韩国19禁主播vip福利视频| 亚洲精品在线看| 亚洲日韩中文字幕| 欧美极品美女电影一区| 国产精品成人aaaaa网站| 日韩中文字幕网| 欧美精品少妇videofree| www.亚洲免费视频| 亚洲成人久久网| 国产精品成人播放| 国产精品久久久久久久久免费看| 91免费精品视频| 成人av在线亚洲| 国产精品在线看| 深夜成人在线观看| 午夜精品美女自拍福到在线| 亚洲一区二区三区在线视频| 成人福利网站在线观看11| 国产精品99久久久久久久久| 国产精品美女免费| 日本精品一区二区三区在线播放视频| 久久久久久久久综合| 久久综合久中文字幕青草| 久久99精品久久久久久琪琪| 国产午夜精品一区理论片飘花| 亚洲精品国产精品国产自| 欧美亚洲第一页| 国内久久久精品| 欧美中文在线视频| 亚洲欧美日韩视频一区| 久久中国妇女中文字幕| 亚洲最大成人网色| 精品久久久久久久大神国产| 日韩激情视频在线播放| 亚洲欧美日韩综合| 国产亚洲福利一区| 国产精品wwww| 欧美中文字幕视频| 美女福利精品视频| 另类色图亚洲色图| 爱福利视频一区| 精品在线小视频| 亚洲精品国产精品国自产在线| 91久久精品在线| 8050国产精品久久久久久| 欧美成人精品激情在线观看| 色婷婷成人综合| 国产91露脸中文字幕在线| 91成人在线视频| 欧美有码在线视频| 自拍偷拍免费精品| 亚洲肉体裸体xxxx137| 亚洲伦理中文字幕| 美女精品久久久| 欧美日韩激情美女| 亚洲激情第一页| 久久精视频免费在线久久完整在线看| 日韩精品在线免费| 欧美高清激情视频| 日韩欧美中文字幕在线观看| 日韩av免费在线播放| 国产精品老女人视频| 国产91色在线播放|