• <fieldset id="8imwq"><menu id="8imwq"></menu></fieldset>
  • <bdo id="8imwq"><input id="8imwq"></input></bdo>
    最新文章專題視頻專題問答1問答10問答100問答1000問答2000關鍵字專題1關鍵字專題50關鍵字專題500關鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關鍵字專題關鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
    問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
    當前位置: 首頁 - 科技 - 知識百科 - 正文

    基于React+Redux的SSR實現(xiàn)方法

    來源:懂視網(wǎng) 責編:小采 時間:2020-11-27 22:12:05
    文檔

    基于React+Redux的SSR實現(xiàn)方法

    基于React+Redux的SSR實現(xiàn)方法:為什么要實現(xiàn)服務端渲染(SSR) 總結下來有以下幾點: SEO,讓搜索引擎更容易讀取頁面內容 首屏渲染速度更快(重點),無需等待js文件下載執(zhí)行的過程 代碼同構,服務端和客戶端可以共享某些代碼 今天我們將構建一個使用 Redux 的簡單的 React 應用程
    推薦度:
    導讀基于React+Redux的SSR實現(xiàn)方法:為什么要實現(xiàn)服務端渲染(SSR) 總結下來有以下幾點: SEO,讓搜索引擎更容易讀取頁面內容 首屏渲染速度更快(重點),無需等待js文件下載執(zhí)行的過程 代碼同構,服務端和客戶端可以共享某些代碼 今天我們將構建一個使用 Redux 的簡單的 React 應用程

    為什么要實現(xiàn)服務端渲染(SSR)

    總結下來有以下幾點:

    1. SEO,讓搜索引擎更容易讀取頁面內容
    2. 首屏渲染速度更快(重點),無需等待js文件下載執(zhí)行的過程
    3. 代碼同構,服務端和客戶端可以共享某些代碼

    今天我們將構建一個使用 Redux 的簡單的 React 應用程序,實現(xiàn)服務端渲染(SSR)。該示例包括異步數(shù)據(jù)抓取,這使得任務變得更有趣。

    如果您想使用本文中討論的代碼,請查看GitHub: answer518/react-redux-ssr

    安裝環(huán)境

    在開始編寫應用之前,需要我們先把環(huán)境編譯/打包環(huán)境配置好,因為我們采用的是es6語法編寫代碼。我們需要將代碼編譯成es5代碼在瀏覽器或node環(huán)境中執(zhí)行。

    我們將用babelify轉換來使用browserify和watchify來打包我們的客戶端代碼。對于我們的服務器端代碼,我們將直接使用babel-cli。

    代碼結構如下:

    build
    src
     ├── client
     │ └── client.js
     └── server
     └── server.js
    

    我們在package.json里面加入以下兩個命令腳本:

    "scripts": {
     "build": "
     browserify ./src/client/client.js -o ./build/bundle.js -t babelify &&
     babel ./src/ --out-dir ./build/",
     "watch": "
     concurrently 
     \"watchify ./src/client/client.js -o ./build/bundle.js -t babelify -v\"
     \"babel ./src/ --out-dir ./build/ --watch\"
     "
    }
    

    concurrently庫幫助并行運行多個進程,這正是我們在監(jiān)控更改時需要的。

    最后一個有用的命令,用于運行我們的http服務器:

    "scripts": {
     "build": "...",
     "watch": "...",
     "start": "nodemon ./build/server/server.js"
    }
    

    不使用 node ./build/server/server.js 而使用 Nodemon 的原因是,它可以監(jiān)控我們代碼中的任何更改,并自動重新啟動服務器。這一點在開發(fā)過程會非常有用。

    開發(fā)React+Redux應用

    假設服務端返回以下的數(shù)據(jù)格式:

    [
     {
     "id": 4,
     "first_name": "Gates",
     "last_name": "Bill",
     "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/marcoramires/128.jpg"
     },
     {
     ...
     }
    ]
    

    我們通過一個組件將數(shù)據(jù)渲染出來。在這個組件的 componentWillMount 生命周期方法中,我們將觸發(fā)數(shù)據(jù)獲取,一旦請求成功,我們將發(fā)送一個類型為 user_fetch 的操作。該操作將由一個 reducer 處理,我們將在 Redux 存儲中獲得更新。狀態(tài)的改變將觸發(fā)我們的組件重新呈現(xiàn)指定的數(shù)據(jù)。

    Redux具體實現(xiàn)

    reducer 處理過程如下:

    // reducer.js
    import { USERS_FETCHED } from './constants';
    
    function getInitialState() {
     return { users: null };
    }
    
    const reducer = function (oldState = getInitialState(), action) {
     if (action.type === USERS_FETCHED) {
     return { users: action.response.data };
     }
     return oldState;
    };
    
    

    為了能派發(fā) action 請求去改變應用狀態(tài),我們需要編寫 Action Creator :

    // actions.js
    import { USERS_FETCHED } from './constants';
    export const usersFetched = response => ({ type: USERS_FETCHED, response });
    
    // selectors.js
    export const getUsers = ({ users }) => users;
    
    

    Redux 實現(xiàn)的最關鍵一步就是創(chuàng)建 Store :

    // store.js
    import { USERS_FETCHED } from './constants';
    import { createStore } from 'redux';
    import reducer from './reducer';
    
    export default () => createStore(reducer);
    
    

    為什么直接返回的是工廠函數(shù)而不是 createStore(reducer) ?這是因為當我們在服務器端渲染時,我們需要一個全新的 Store 實例來處理每個請求。

    實現(xiàn)React組件

    在這里需要提的一個重點是,一旦我們想實現(xiàn)服務端渲染,那我們就需要改變之前的純客戶端編程模式。

    服務器端渲染,也叫代碼同構,也就是同一份代碼既能在客戶端渲染,又能在服務端渲染。

    我們必須保證代碼能在服務端正常的運行。例如,訪問 Window 對象,Node不提供Window對象的訪問。

    // App.jsx
    import React from 'react';
    import { connect } from 'react-redux';
    
    import { getUsers } from './redux/selectors';
    import { usersFetched } from './redux/actions';
    
    const ENDPOINT = 'http://localhost:3000/users_fake_data.json';
    
    class App extends React.Component {
     componentWillMount() {
     fetchUsers();
     }
     render() {
     const { users } = this.props;
    
     return (
     <div>
     {
     users && users.length > 0 && users.map(
     // ... render the user here
     )
     }
     </div>
     );
     }
    }
    
    const ConnectedApp = connect(
     state => ({
     users: getUsers(state)
     }),
     dispatch => ({
     fetchUsers: async () => dispatch(
     usersFetched(await (await fetch(ENDPOINT)).json())
     )
     })
    )(App);
    
    export default ConnectedApp;
    
    

    你看到,我們使用 componentWillMount 來發(fā)送 fetchUsers 請求, componentDidMount 為什么不能用呢? 主要原因是 componentDidMount 在服務端渲染過程中并不會執(zhí)行。

    fetchUsers 是一個異步函數(shù),它通過Fetch API請求數(shù)據(jù)。當數(shù)據(jù)返回時,會派發(fā) users_fetch 動作,從而通過 reducer 重新計算狀態(tài),而我們的 <App /> 由于連接到 Redux 從而被重新渲染。

    // client.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { Provider } from 'react-redux';
    
    import App from './App.jsx';
    import createStore from './redux/store';
    
    ReactDOM.render(
     <Provider store={ createStore() }><App /></Provider>,
     document.querySelector('#content')
    );
    
    

    運行Node Server

    為了演示方便,我們首選Express作為http服務器。

    // server.js
    import express from 'express';
    
    const app = express();
    
    // Serving the content of the "build" folder. Remember that
    // after the transpiling and bundling we have:
    //
    // build
    // ├── client
    // ├── server
    // │ └── server.js
    // └── bundle.js
    app.use(express.static(__dirname + '/../'));
    
    app.get('*', (req, res) => {
     res.set('Content-Type', 'text/html');
     res.send(`
     <html>
     <head>
     <title>App</title>
     </head>
     <body>
     <div id="content"></div>
     <script src="https://www.gxlcms.com/bundle.js"></script>
     </body>
     </html>
     `);
    });
    
    app.listen(
     3000,
     () => console.log('Example app listening on port 3000!')
    );
    
    

    有了這個文件,我們可以運行 npm run start 并訪問 http://localhost:3000 。我們看到數(shù)據(jù)獲取成功,并成功的顯示了。

    服務端渲染

    目前為止,我們的服務端僅僅是返回了一個 html 骨架,而所有交互全在客戶端完成。瀏覽器需要先下載 bundle.js 后執(zhí)行。而服務端渲染的作用就是在服務器上執(zhí)行所有操作并發(fā)送最終標記,而不是把所有工作交給瀏覽器執(zhí)行。 React 足夠的聰明,能夠識別出這些標記。

    還記得我們在客戶端做的以下事情嗎?

    import ReactDOM from 'react-dom';
    
    ReactDOM.render(
     <Provider store={ createStore() }><App /></Provider>,
     document.querySelector('#content')
    );
    
    

    服務端幾乎相同:

    import ReactDOMServer from 'react-dom/server';
    
    const markupAsString = ReactDOMServer.renderToString(
     <Provider store={ store }><App /></Provider>
    );
    
    

    我們使用了相同的組件 <App /> 和 store ,不同之處在于它返回的是一個字符串,而不是虛擬DOM。

    然后將這個字符串加入到 Express 的響應里面,所以服務端代碼為:

    const store = createStore();
    const content = ReactDOMServer.renderToString(
     <Provider store={ store }><App /></Provider>
    );
    
    app.get('*', (req, res) => {
     res.set('Content-Type', 'text/html');
     res.send(`
     <html>
     <head>
     <title>App</title>
     </head>
     <body>
     <div id="content">${ content }</div>
     <script src="https://www.gxlcms.com/bundle.js"></script>
     </body>
     </html>
     `);
    });
    
    

    如果重新啟動服務器并打開相同的 http://localhost:3000 ,我們將看到以下響應:

    <html>
     <head>
     <title>App</title>
     </head>
     <body>
     <div id="content"><div data-reactroot=""></div></div>
     <script src="https://www.gxlcms.com/bundle.js"></script>
     </body>
    </html>
    

    我們的頁面中確實有一些內容,但它只是 <div data-reactroot=""></div> 。這并不意味著程序出錯了。這絕對是正確的。 React 確實呈現(xiàn)了我們的頁面,但它只呈現(xiàn)靜態(tài)內容。在我們的組件中,我們在獲取數(shù)據(jù)之前什么都沒有,數(shù)據(jù)的獲取是一個異步過程,在服務器上呈現(xiàn)時,我們必須考慮到這一點。這就是我們的任務變得棘手的地方。這可以歸結為我們的應用程序在做什么。在本例中,客戶端代碼依賴于一個特定的請求,但如果使用 redux-saga 庫,則可能是多個請求,或者可能是一個完整的root saga。我意識到處理這個問題的兩種方法:

    1、我們明確知道請求的頁面需要什么樣的數(shù)據(jù)。我們獲取數(shù)據(jù)并使用該數(shù)據(jù)創(chuàng)建 Redux 存儲。然后我們通過提供已完成的 Store 來呈現(xiàn)頁面,理論上我們可以做到。

    2、我們完全依賴于運行在客戶端上的代碼,計算出最終的結果。

    第一種方法,需要我們在兩端做好狀態(tài)管理。第二種方法需要我們在服務端使用一些額外的庫或工具,來確保同一套代碼能在服務端和客戶端做相同的事情,我個人比較推薦使用這種方法。

    例如,我們使用了 Fetch API 向后端發(fā)出異步請求,而服務端默認是不支持的。我們需要做的就是在 server.js 中將 Fetch 導入:

    import 'isomorphic-fetch';
    

    我們使用客戶端API接收異步數(shù)據(jù),一旦 Store 獲取到異步數(shù)據(jù),我們將觸發(fā) ReactDOMServer.renderToString 。它會提供給我們想要的標記。我們的Express處理器是這樣的:

    app.get('*', (req, res) => {
     const store = createStore();
    
     const unsubscribe = store.subscribe(() => {
     const users = getUsers(store.getState());
    
     if (users !== null && users.length > 0) {
     unsubscribe();
    
     const content = ReactDOMServer.renderToString(
     <Provider store={ store }><App /></Provider>
     );
    
     res.set('Content-Type', 'text/html');
     res.send(`
     <html>
     <head>
     <title>App</title>
     </head>
     <body>
     <div id="content">${ content }</div>
     <script src="https://www.gxlcms.com/bundle.js"></script>
     </body>
     </html>
     `);
     }
     });
    
     ReactDOMServer.renderToString(<Provider store={ store }><App /></Provider>);
    });

    我們使用 Store subscribe 方法來監(jiān)聽狀態(tài)。當狀態(tài)發(fā)生變化——是否有任何用戶數(shù)據(jù)被獲取。如果 users 存在,我們將 unsubscribe() ,這樣我們就不會讓相同的代碼運行兩次,并且我們使用相同的存儲實例轉換為string。最后,我們將標記輸出到瀏覽器。

    store.subscribe方法返回一個函數(shù),調用這個函數(shù)就可以解除監(jiān)聽

    有了上面的代碼,我們的組件已經(jīng)可以成功地在服務器端渲染。通過開發(fā)者工具,我們可以看到發(fā)送到瀏覽器的內容:

    <html>
     <head>
     <title>App</title>
     <style>
     body {
     font-size: 18px;
     font-family: Verdana;
     }
     </style>
     </head>
     <body>
     <div id="content"><div data-reactroot=""><p>Eve Holt</p><p>Charles Morris</p><p>Tracey Ramos</p></div></div>
     <script>
     window.__APP_STATE = {"users":[{"id":4,"first_name":"Eve","last_name":"Holt","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/marcoramires/128.jpg"},{"id":5,"first_name":"Charles","last_name":"Morris","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/stephenmoon/128.jpg"},{"id":6,"first_name":"Tracey","last_name":"Ramos","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/bigmancho/128.jpg"}]};
     </script>
     <script src="https://www.gxlcms.com/bundle.js"></script>
     </body>
     </html>
    

    當然,現(xiàn)在并沒有結束,客戶端 JavaScript 不知道服務器上發(fā)生了什么,也不知道我們已經(jīng)對API進行了請求。我們必須通過傳遞 Store 的狀態(tài)來通知瀏覽器,以便它能夠接收它。

    const content = ReactDOMServer.renderToString(
     <Provider store={ store }><App /></Provider>
    );
    
    res.set('Content-Type', 'text/html');
    res.send(`
     <html>
     <head>
     <title>App</title>
     </head>
     <body>
     <div id="content">${ content }</div>
     <script>
     window.__APP_STATE = ${ JSON.stringify(store.getState()) };
     </script>
     <script src="https://www.gxlcms.com/bundle.js"></script>
     </body>
     </html>
    `);
    
    

    我們將 Store 狀態(tài)放到一個全局變量 __APP_STATE 中, reducer 也有一點變化:

    function getInitialState() {
     if (typeof window !== 'undefined' && window.__APP_STATE) {
     return window.__APP_STATE;
     }
     return { users: null };
    }
    

    注意 typeof window !== 'undefined' ,我們必須這樣做,因為這段代碼也會在服務端執(zhí)行,這就是為什么說在做服務端渲染時要非常小心,尤其是全局使用的瀏覽器api的時候。

    最后一個需要優(yōu)化的地方,就是當已經(jīng)取到 users 時,必須阻止 fetch 。

    componentWillMount() {
     const { users, fetchUsers } = this.props;
    
     if (users === null) {
     fetchUsers();
     }
    }
    
    

    總結

    服務器端呈現(xiàn)是一個有趣的話題。它有很多優(yōu)勢,并改善了整體用戶體驗。它還會提升你的單頁應用程序的SEO。但這一切并不簡單。在大多數(shù)情況下,需要額外的工具和精心選擇的api。

    這只是一個簡單的案例,實際開發(fā)場景往往比這個復雜的多,需要考慮的情況也會非常多,你們的服務端渲染是怎么做的?

    聲明:本網(wǎng)頁內容旨在傳播知識,若有侵權等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

    文檔

    基于React+Redux的SSR實現(xiàn)方法

    基于React+Redux的SSR實現(xiàn)方法:為什么要實現(xiàn)服務端渲染(SSR) 總結下來有以下幾點: SEO,讓搜索引擎更容易讀取頁面內容 首屏渲染速度更快(重點),無需等待js文件下載執(zhí)行的過程 代碼同構,服務端和客戶端可以共享某些代碼 今天我們將構建一個使用 Redux 的簡單的 React 應用程
    推薦度:
    標簽: ssr 實現(xiàn) React
    • 熱門焦點

    最新推薦

    猜你喜歡

    熱門推薦

    專題
    Top
    主站蜘蛛池模板: 国产福利在线观看精品| 久久99国产精品久久99| 99RE6热在线精品视频观看| 热RE99久久精品国产66热| 日韩一级精品视频在线观看| 久久国产乱子伦免费精品| 欧美精品福利在线视频| 一本久久a久久精品综合夜夜| 国产亚洲色婷婷久久99精品| 日韩精品无码永久免费网站 | 天天爽夜夜爽8888视频精品| 国产精品极品美女自在线观看免费| 国产亚洲精品国产| 久久国产欧美日韩精品| 亚洲精品国产福利一二区| 国产三级国产精品国产普男人| 国产精品女同一区二区| 亚洲电影日韩精品 | 国产亚洲精品线观看动态图| 国产精品莉莉欧美自在线线| 久久精品国产亚洲AV嫖农村妇女| 亚洲精品视频久久久| 欧美成人精品欧美一级乱黄码| 囯产精品一区二区三区线| 日本精品久久久久中文字幕| 国产精品久久久久久影院| 成人久久精品一区二区三区| 99精品热这里只有精品| 国产精品久久久久国产A级| 国产精品igao视频网网址| 久久香蕉国产线看观看精品yw| 熟妇无码乱子成人精品| 无码精品人妻一区二区三区免费看| 色久综合网精品一区二区| 精品无码AV无码免费专区| 精品人妻大屁股白浆无码| 国产精品亚洲аv无码播放| 国产精品多人p群无码| 国产l精品国产亚洲区在线观看| 92国产精品午夜福利| 久久久国产精品福利免费|