專案原始碼
拉了就跑
yarn
npm run ssr
Gif預覽效果
SSR:服務端渲染,在服務端將首屏的html直接返回給客戶端
- SEO:讓各爬蟲能爬到首屏的內容,匹配到搜尋關鍵字,提高在百度谷歌的排序
- 加快首屏速度,不再是載入完js然後
ReactDom.render('#app', Com)
,讓伺服器分擔部分渲染的壓力 - 詳細看參考文件
精簡易懂的內容來了
SSR怎麼實現首屏渲染
- 通過express服務來返回給瀏覽器首屏的HTML
- HTML是由
react-dom/server
提供的renderToString
來生成,理論依據是node雖然不能識別HTML和JSX,但React能生成虛擬DOM,然後插入HTML直接返回給瀏覽器識別渲染
import { renderToString } from "react-dom/server";
import { StaticRouter } from "react-router-dom";
const app = express();
app.get( "/*", (req, res) => {
const context = {}; // 上下文物件,可以儲存有關渲染的資訊
const jsx = (
<StaticRouter context={ context } location={ req.url }>
<App />
</StaticRouter>
);
const reactDom = renderToString( jsx ); // 在服務端將JSX渲染為HTML
res.writeHead( 200, { "Content-Type": "text/html" } );
res.end(`
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="icon" href="data:;base64,=">
<title>React SSR</title>
</head>
<body>
<div id="app">${ reactDom }</div>
<script src="/index.js"></script> // 注意這裡,服務端返回的HTMl會去載入webpack打包好的SPA應用,然後客戶端接管應用,繫結事件,處理路由跳轉和其他Ajax請求
</body>
</html>
`);
});
app.listen(3000, () => {
console.log('Running on http://localhost:3000/');
});
複製程式碼
const jsx = (
<Provider store={store}>
<Router>
<Layout />
</Router>
</Provider>
);
const app = document.getElementById( "app" );
ReactDOM.hydrate( jsx, app ); // 客戶端接管應用後,React會將服務端渲染的標記載入,並將嘗試將事件偵聽器附加到現有標記
複製程式碼
結合Router
- 服務端通過StaticRouter來渲染對應的元件,根據瀏覽器請求的url來匹配路由並返回對應元件
- 返回給瀏覽器後,載入webpack打包好的客戶端js,然後接管頁面
- 具體看程式碼哈
結合Redux
- 首屏頁一般要請求介面渲染對應資料
- 將請求的資料設定到狀態樹上,並將JSON一併返回給瀏覽器
<script> window.REDUX_DATA = ${ JSON.stringify( state ) } </script> 複製程式碼
- 客戶端接管應用後將資料初始化到客戶端的狀態樹上
const store = createClientStore( window.REDUX_DATA ); const jsx = ( <Provider store={store}> <Router> <Layout /> </Router> </Provider> ); 複製程式碼
怎麼按需載入
- 使用
react-lodable
按需載入,其原理是import().then()
,webpack在識別到後就能程式碼分割了 - 其會生成
react-loadable.json
來標識需要動態載入的元件const jsx = ( <Provider store={store}> <Loadable.Capture report={moduleName => modules.push(moduleName)}> // 需要懶載入的模組,最後在返回的HTML中去遍歷 <StaticRouter context={ context } location={ req.url }> <Layout /> </StaticRouter> </Loadable.Capture> </Provider> ); ${bundles.map(bundle => { return `<script src="/${bundle.file}"></script>` // alternatively if you are using publicPath option in webpack config // you can use the publicPath value from bundle, e.g: // return `<script src="${bundle.publicPath}"></script>` }).join('\n')} 複製程式碼
- lodable實現原理
Lodable({ loader: ()=> import(/* webpackChunkName: 'Hello' */'./Hello'), loading, }) class Lodable { componentWillMount() { this.cancelUpdate = false; const { loader } = this.props; loader.then(Com=> { this.Com = Com; if(!this.cancelUpdate) { thi.forceUpdate(); // 初次懶載入完後重新渲染 } }) } componentWillUnmount() { this.cancelUpdate = true; } render() { const { comProps } = this.props; return this.Com ? ( <this.Com.default {...comProps} /> ) : ( <this.Com {...comProps}/> ) } } 複製程式碼
服務端怎麼處理CSS和圖片
- 服務端還是通過webpack來編譯對應的node端的程式碼,來實現對CSS和圖片資源的引用和處理
- 其實這裡只要理解客戶端和服務端分別打包,最後node執行服務端的js,返回HTML後載入客戶端js,實現客戶端接管應用
- 處理靜態資源還有
webpack-isomorphic-tools
,universal-webpack
,前者作者已經不更新了,我後面發現其實用webpack直接處理簡單多了,可能是應用場景還用不到const serverConfig = { target: 'node', // 標記為node端 mode: 'development', entry: './src/server', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'build') }, externals: [nodeExternals()], // 不會講node_modules打包進去 module: { rules: [{ test: /\.js?$/, loader: 'babel-loader', exclude: /node_modules/, options: { cacheDirectory: true, plugins: ['transform-decorators-legacy'], presets: ["react", ["env", { "targets": { "node": "current" } }], "stage-0"], } }, { test: /\.css?$/, use: ['isomorphic-style-loader', { // 處理CSS服務端的loader loader: 'css-loader', options: { importLoaders: 1, modules: true, // 開啟css-module localIdentName: '[name]_[local]_[hash:base64:5]' //生成的class的命名規則 } }] }, { test: /\.(png|jpeg|jpg|gif|svg)?$/, loader: 'url-loader', options: { limit: 8000, outputPath: '../build/', publicPath: '/' } }] }, plugins: [ new ReactLoadablePlugin({ filename: './build/react-loadable.json', // 按需載入的配置檔案 }), ] }; 複製程式碼
參考文件
接下來準備做個
結合TS的服務端渲染全網免費聽歌SPA應用(Jay Chou的也有的那種)
- 首頁,排行榜,我的
- 將周杰倫的歌爬到mongoDB或者存在記憶體中
- 結合Aplayer
- 結合頻譜圖
- 看情況支援搜尋其他歌手和歌曲