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

首頁 > 編程 > JavaScript > 正文

利用React Router4實現的服務端直出渲染(SSR)

2019-11-19 12:18:57
字體:
來源:轉載
供稿:網友

我們已經熟悉React 服務端渲染(SSR)的基本步驟,現在讓我們更進一步利用 React RouterV4 實現客戶端和服務端的同構。畢竟大多數的應用都需要用到web前端路由器,所以要讓SSR能夠正常的運行,了解路由器的設置是十分有必要的

基本步驟

路由器配置

前言已經簡單的介紹了React SSR,首先我們需要添加ReactRouter4到我們的項目中

$ yarn add react-router-dom# or, using npm$ npm install react-router-dom

接著我們會描述一個簡單的場景,其中組件是靜態的且不需要去獲取外部數據。我們會在這個基礎之上去了解如何完成取到數據的服務端渲染。

在客戶端,我們只需像以前一樣將我們的的App組件通過ReactRouter的BrowserRouter來包起來。

src/index.js

import React from 'react';import ReactDOM from 'react-dom';import { BrowserRouter } from 'react-router-dom';import App from './App';ReactDOM.hydrate( <BrowserRouter>  <App /> </BrowserRouter>, document.getElementById('root'));

在服務端我們將采取類似的方式,但是改為使用無狀態的 StaticRouter

server/index.js

app.get('/*', (req, res) => { const context = {}; const app = ReactDOMServer.renderToString(  <StaticRouter location={req.url} context={context}>   <App />  </StaticRouter> ); const indexFile = path.resolve('./build/index.html'); fs.readFile(indexFile, 'utf8', (err, data) => {  if (err) {   console.error('Something went wrong:', err);   return res.status(500).send('Oops, better luck next time!');  }  return res.send(   data.replace('<div id="root"></div>', `<div id="root">${app}</div>`)  ); });});app.listen(PORT, () => { console.log(`😎 Server is listening on port ${PORT}`);});

StaticRouter組件需要 location和context屬性。我們傳遞當前的url(Express req.url)給location,設置一個空對象給context。context對象用于存儲特定的路由信息,這個信息將會以staticContext的形式傳遞給組件

運行一下程序看看結果是否我們所預期的,我們給App組件添加一些路由信息

src/App.js

import React from 'react';import { Route, Switch, NavLink } from 'react-router-dom';import Home from './Home';import Posts from './Posts';import Todos from './Todos';import NotFound from './NotFound';export default props => { return (  <div>   <ul>    <li>     <NavLink to="/">Home</NavLink>    </li>    <li>     <NavLink to="/todos">Todos</NavLink>    </li>    <li>     <NavLink to="/posts">Posts</NavLink>    </li>   </ul>   <Switch>    <Route     exact     path="/"     render={props => <Home name="Alligator.io" {...props} />}    />    <Route path="/todos" component={Todos} />    <Route path="/posts" component={Posts} />    <Route component={NotFound} />   </Switch>  </div> );};

現在如果你運行一下程序($ yarn run dev),我們的路由在服務端被渲染,這是我們所預期的。

利用404狀態來處理未找到資源的網絡請求

我們做一些改進,當渲染NotFound組件時讓服務端使用404HTTP狀態碼來響應。首先我們將一些信息放到NotFound組件的staticContext

import React from 'react';export default ({ staticContext = {} }) => { staticContext.status = 404; return <h1>Oops, nothing here!</h1>;};

然后在服務端,我們可以檢查context對象的status屬性是否是404,如果是404,則以404狀態響應服務端請求。

server/index.js

// ...app.get('/*', (req, res) => { const context = {}; const app = ReactDOMServer.renderToString(  <StaticRouter location={req.url} context={context}>   <App />  </StaticRouter> ); const indexFile = path.resolve('./build/index.html'); fs.readFile(indexFile, 'utf8', (err, data) => {  if (err) {   console.error('Something went wrong:', err);   return res.status(500).send('Oops, better luck next time!');  }  if (context.status === 404) {   res.status(404);  }  return res.send(   data.replace('<div id="root"></div>', `<div id="root">${app}</div>`)  ); });});// ...

重定向

補充一下,我們可以做一些類似重定向的工作。如果我們有使用Redirect組件,ReactRouter會自動添加重定向的url到context對象的屬性上。

server/index.js (部分)

if (context.url) { return res.redirect(301, context.url);}

讀取數據

有時候我們的服務端渲染應用需要數據呈現,我們需要用一種靜態的方式來定義我們的路由而不是只涉及到客戶端的動態的方式。失去定義動態路由的定義是服務端渲染最適合所需要的應用的原因(譯者注:這句話的意思應該是SSR不允許路由是動態定義的)。

我們將使用fetch在客戶端和服務端,我們增加isomorphic-fetch到我們的項目。同時我們也增加serialize-javascript這個包,它可以方便的序列化服務器上獲取到的數據。

$ yarn add isomorphic-fetch serialize-javascript# or, using npm:$ npm install isomorphic-fetch serialize-javascript

我們定義我們的路由信息為一個靜態數組在routes.js文件里

src/routes.js

import App from './App';import Home from './Home';import Posts from './Posts';import Todos from './Todos';import NotFound from './NotFound';import loadData from './helpers/loadData';const Routes = [ {  path: '/',  exact: true,  component: Home }, {  path: '/posts',  component: Posts,  loadData: () => loadData('posts') }, {  path: '/todos',  component: Todos,  loadData: () => loadData('todos') }, {  component: NotFound }];export default Routes;

有一些路由配置現在有一個叫loadData的鍵,它是一個調用loadData函數的函數。這個是我們的loadData函數的實現

helpers/loadData.js

import 'isomorphic-fetch';export default resourceType => { return fetch(`https://jsonplaceholder.typicode.com/${resourceType}`)  .then(res => {   return res.json();  })  .then(data => {   // only keep 10 first results   return data.filter((_, idx) => idx < 10);  });};

我們簡單的使用fetch來從REST API 獲取數據

在服務端我們將使用ReactRouter的matchPath去尋找當前url所匹配的路由配置并判斷它有沒有loadData屬性。如果是這樣,我們調用loadData去獲取數據并把數據放到全局window對象中在服務器的響應中

server/index.js

import React from 'react';import express from 'express';import ReactDOMServer from 'react-dom/server';import path from 'path';import fs from 'fs';import serialize from 'serialize-javascript';import { StaticRouter, matchPath } from 'react-router-dom';import Routes from '../src/routes';import App from '../src/App';const PORT = process.env.PORT || 3006;const app = express();app.use(express.static('./build'));app.get('/*', (req, res) => { const currentRoute =  Routes.find(route => matchPath(req.url, route)) || {}; let promise; if (currentRoute.loadData) {  promise = currentRoute.loadData(); } else {  promise = Promise.resolve(null); } promise.then(data => {  // Lets add the data to the context  const context = { data };  const app = ReactDOMServer.renderToString(   <StaticRouter location={req.url} context={context}>    <App />   </StaticRouter>  );  const indexFile = path.resolve('./build/index.html');  fs.readFile(indexFile, 'utf8', (err, indexData) => {   if (err) {    console.error('Something went wrong:', err);    return res.status(500).send('Oops, better luck next time!');   }   if (context.status === 404) {    res.status(404);   }   if (context.url) {    return res.redirect(301, context.url);   }   return res.send(    indexData     .replace('<div id="root"></div>', `<div id="root">${app}</div>`)     .replace(      '</body>',      `<script>window.__ROUTE_DATA__ = ${serialize(data)}</script></body>`     )   );  }); });});app.listen(PORT, () => { console.log(`😎 Server is listening on port ${PORT}`);});

請注意,我們添加組件的數據到context對象。在服務端渲染中我們將通過staticContext來訪問它。

現在我們可以在需要加載時獲取數據的組件的構造函數和componentDidMount方法里添加一些判斷

src/Todos.js

import React from 'react';import loadData from './helpers/loadData';class Todos extends React.Component { constructor(props) {  super(props);  if (props.staticContext && props.staticContext.data) {   this.state = {    data: props.staticContext.data   };  } else {   this.state = {    data: []   };  } } componentDidMount() {  setTimeout(() => {   if (window.__ROUTE_DATA__) {    this.setState({     data: window.__ROUTE_DATA__    });    delete window.__ROUTE_DATA__;   } else {    loadData('todos').then(data => {     this.setState({      data     });    });   }  }, 0); } render() {  const { data } = this.state;  return <ul>{data.map(todo => <li key={todo.id}>{todo.title}</li>)}</ul>; }}export default Todos;

工具類

ReactRouterConfig是由ReactRouter團隊提供和維護的包。它提供了兩個處理ReactRouter和SSR更便捷的工具matchRoutes和renderRoutes。

matchRoutes

前面的例子都非常簡單都,都沒有嵌套路由。有時在多路由的情況下,使用matchPath是行不通的,因為它只能匹配一條路由。matchRoutes是一個能幫助我們匹配多路由的工具。

這意味著在匹配路由的過程中我們可以往一個數組里存放promise,然后調用promise.all去解決所有匹配到的路由的取數邏輯。

import { matchRoutes } from 'react-router-config';// ...const matchingRoutes = matchRoutes(Routes, req.url);let promises = [];matchingRoutes.forEach(route => { if (route.loadData) {  promises.push(route.loadData()); }});Promise.all(promises).then(dataArr => { // render our app, do something with dataArr, send response});// ...

renderRoutes

renderRoutes接收我們的靜態路由配置對象并返回所需的Route組件。為了matchRoutes能適當的工作renderRoutes應該被使用。

通過使用renderRoutes,我們的程序改成了一個更簡潔的形式。

src/App.js

import React from 'react';import { renderRoutes } from 'react-router-config';import { Switch, NavLink } from 'react-router-dom';import Routes from './routes';import Home from './Home';import Posts from './Posts';import Todos from './Todos';import NotFound from './NotFound';export default props => { return (  <div>   {/* ... */}   <Switch>    {renderRoutes(Routes)}   </Switch>  </div> );};

譯者注

  • SSR服務端React組件的生命周期不會運行到componentDidMount,componentDidMount只有在客戶端才會運行。
  • React16不再推薦使用componentWillMount方法,應使用constructor來代替。
  • staticContext的實現應該跟redux的高階組件connect類似,也是通過包裝一層react控件來實現子組件的屬性傳遞。
  • 文章只是對SSR做了一個入門的介紹,如Loadable和樣式的處理在文章中沒有介紹,但這兩點對于SSR來說很重要,以后找機會寫一篇相關的博文

原文地址

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

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美日韩视频在线| 国产情人节一区| 一区二区三区精品99久久| 日韩av网站导航| 九九九久久久久久| 欧美精品videosex性欧美| 久久综合色88| 美乳少妇欧美精品| 日韩欧美亚洲成人| 国产精品成人一区| 亚洲国模精品私拍| 中日韩美女免费视频网址在线观看| 欧洲亚洲在线视频| 亚洲最大福利网站| 亚洲国产成人精品电影| 国产精品久久久久久久久借妻| 成人免费看黄网站| 热久久99这里有精品| 久久在线观看视频| 欧美在线视频免费观看| 日韩欧美在线一区| 日韩欧美有码在线| 亚洲成人久久一区| 久久99亚洲热视| 亚洲精品456在线播放狼人| 亚洲欧美中文日韩在线| 亚洲精品www久久久| 久久久亚洲精品视频| 国外视频精品毛片| 久久夜色精品国产| 久久精品国产2020观看福利| 欧美孕妇孕交黑巨大网站| 91在线观看欧美日韩| 91精品国产99| 日韩在线免费av| 亚洲精品av在线播放| 色偷偷88888欧美精品久久久| 欧美日韩激情小视频| 亚洲精品久久久久久久久久久久| 清纯唯美日韩制服另类| 亚洲精品久久久久久久久| 国产精品一区二区在线| 日韩av网站导航| 亚洲一区二区久久久久久久| 91精品国产综合久久香蕉| 国外成人性视频| 亚洲bt天天射| 中文字幕久精品免费视频| 亚洲精品福利资源站| 亚洲无亚洲人成网站77777| 欧美日韩综合视频| 国产成人精品日本亚洲| 亚洲第一页中文字幕| 97精品欧美一区二区三区| 日本老师69xxx| 久久久久久九九九| 久久69精品久久久久久国产越南| 成人网在线免费看| 中文字幕在线日韩| 精品成人国产在线观看男人呻吟| 亚洲区bt下载| 午夜精品久久久久久99热| 秋霞午夜一区二区| 亚洲欧美制服中文字幕| 国产精品亚洲欧美导航| 欧美黄色片视频| 92国产精品视频| 国产亚洲欧洲高清| 国产午夜精品一区二区三区| 欧美丰满少妇xxxx| 91视频国产精品| 国产成人极品视频| 亚洲欧美综合另类中字| 亚洲第一av网站| 国产精品久久一区主播| 亚洲第一精品夜夜躁人人爽| 欧美美女操人视频| 亚洲欧美另类国产| 国产亚洲一区二区精品| 日韩在线观看电影| 久久久在线观看| 亚洲成人精品视频| 亚洲视频777| 91久久久亚洲精品| 亚洲国产成人精品久久| 国产视频在线一区二区| 欧美激情精品久久久久久变态| 国模精品视频一区二区三区| 国产欧美日韩精品丝袜高跟鞋| 91免费在线视频| 亚洲图中文字幕| 久久精品色欧美aⅴ一区二区| 美女扒开尿口让男人操亚洲视频网站| 精品亚洲一区二区三区| 91色在线观看| 亚洲综合av影视| 91色视频在线导航| 久久精品一区中文字幕| 欧美日韩在线视频一区二区| 国产精品直播网红| 色综合久综合久久综合久鬼88| 欧美疯狂做受xxxx高潮| 欧美福利小视频| 欧美成年人视频网站欧美| 久久免费高清视频| 欧美精品激情在线观看| 亚洲色图第三页| 欧美乱人伦中文字幕在线| 日韩欧美高清视频| 中文字幕亚洲国产| 97精品国产97久久久久久| 亚洲二区中文字幕| 国产日韩亚洲欧美| 91久久精品国产91久久性色| 韩国一区二区电影| 精品国产欧美成人夜夜嗨| 中文字幕亚洲无线码在线一区| 国产99久久精品一区二区| 国产精品嫩草影院久久久| 亚洲aⅴ男人的天堂在线观看| 亚洲理论在线a中文字幕| 亚洲国产日韩欧美在线图片| 亚洲女在线观看| 国产精品亚洲自拍| xxxxx91麻豆| 亚洲人成在线观看| 日本欧美一级片| 国内偷自视频区视频综合| 2021久久精品国产99国产精品| 久久影视电视剧凤归四时歌| 亚洲美女视频网| 九九精品在线观看| 久久好看免费视频| 亚洲欧美国产va在线影院| 久热精品视频在线观看| 45www国产精品网站| 亚洲色图美腿丝袜| 亚洲欧美另类国产| 久久九九国产精品怡红院| 亚洲无av在线中文字幕| 欧美日韩一区二区免费视频| 国产精品久久久久福利| 国产视频精品在线| 午夜精品三级视频福利| 久久在线观看视频| 97国产suv精品一区二区62| 久久这里有精品| 欧美日韩另类视频| 久久久人成影片一区二区三区| 欧美一级高清免费| 亚洲毛片在线观看.| 欧美一级bbbbb性bbbb喷潮片| 国产精品成人免费视频| 精品毛片三在线观看| 久久精品国产成人| 久久资源免费视频| 精品国产一区二区三区久久| 亚洲成人激情在线| 91精品国产高清自在线| 欧美激情一级欧美精品| 亚洲丝袜av一区| 国产视频精品久久久| 国产精品夫妻激情| 91视频九色网站|