<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # 服務端渲染 服務端渲染一個很常見的場景是當用戶(或搜索引擎爬蟲)第一次請求頁面時,用它來做*初始渲染*。當服務器接收到請求后,它把需要的組件渲染成 HTML 字符串,然后把它返回給客戶端(這里統指瀏覽器)。之后,客戶端會接手渲染控制權。 下面我們使用 React 來做示例,對于支持服務端渲染的其它 view 框架,做法也是類似的。 ### 服務端使用 Redux 當在服務器使用 Redux 渲染時,一定要在響應中包含應用的 state,這樣客戶端可以把它作為初始 state。這點至關重要,因為如果在生成 HTML 前預加載了數據,我們希望客戶端也能訪問這些數據。否則,客戶端生成的 HTML 與服務器端返回的 HTML 就會不匹配,客戶端還需要重新加載數據。 把數據發送到客戶端,需要以下步驟: - 為每次請求創建全新的 Redux store 實例; - 按需 dispatch 一些 action; - 從 store 中取出 state; - 把 state 一同返回給客戶端。 在客戶端,使用服務器返回的 state 創建并初始化一個全新的 Redux store。 Redux 在服務端**惟一**要做的事情就是,提供應用所需的**初始 state**。 ### 安裝 下面來介紹如何配置服務端渲染。使用極簡的 [Counter 計數器應用](https://github.com/rackt/redux/tree/master/examples/counter) 來做示例,介紹如何根據請求在服務端提前渲染 state。 ### 安裝依賴庫 本例會使用 [Express](http://expressjs.com/) 來做小型的 web 服務器。引入 [serve-static](https://www.npmjs.com/package/serve-static) middleware 來處理靜態文件,稍后有代碼。 還需要安裝 Redux 對 React 的綁定庫,Redux 默認并不包含。 ~~~ npm install --save express serve-static react-redux ~~~ ### 服務端開發 下面是服務端代碼大概的樣子。使用 [app.use](http://expressjs.com/api.html#app.use) 掛載 [Express middleware](http://expressjs.com/guide/using-middleware.html) 處理所有請求。`serve-static` middleware 以同樣的方式處理來自客戶端的 javascript 文件請求。如果你還不熟悉 Express 或者 middleware,只需要了解每次服務器收到請求時都會調用 handleRender 函數。 > ##### 生產環境使用須知 > 在生產環境中,最好使用類似 nigix 這樣的服務器來處理靜態文件請求,只使用 Node 處理應用請求。雖然這個話題已經超出本教程討論范疇。 ##### `server.js` ~~~ import path from 'path'; import Express from 'express'; import React from 'react'; import { createStore } from 'redux'; import { Provider } from 'react-redux'; import counterApp from './reducers'; import App from './containers/App'; const app = Express(); const port = 3000; // 使用這個 middleware 處理 dist 目錄下的靜態文件請求 app.use(require('serve-static')(path.join(__dirname, 'dist'))); // 每當收到請求時都會觸發 app.use(handleRender); // 接下來會補充這部分代碼 function handleRender(req, res) { /* ... */ } function renderFullPage(html, initialState) { /* ... */ } app.listen(port); ~~~ ### 處理請求 第一件要做的事情就是對每個請求創建一個新的 Redux store 實例。這個 store 惟一作用是提供應用初始的 state。 渲染時,使用 `<Provider>` 來包住根組件 `<App />`,以此來讓組件樹中所有組件都能訪問到 store,就像之前的[搭配 React](#) 教程講的那樣。 服務端渲染最關鍵的一步是在**發送響應前**渲染初始的 HTML。這就要使用 [React.renderToString()](https://facebook.github.io/react/docs/top-level-api.html#react.rendertostring). 然后使用 [`store.getState()`](#) 從 store 得到初始 state。`renderFullPage` 函數會介紹接下來如何傳遞。 ~~~ function handleRender(req, res) { // 創建新的 Redux store 實例 const store = createStore(counterApp); // 把組件渲染成字符串 const html = React.renderToString( <Provider store={store}> {() => <App />} </Provider> ); // 從 store 中獲得 state const initialState = store.getState(); // 把渲染后的頁面內容發送給客戶端 res.send(renderFullPage(html, initialState)); } ~~~ ### 注入初始組件的 HTML 和 State 服務端最后一步就是把初始組件的 HTML 和初始 state 注入到客戶端能夠渲染的模板中。如何傳遞 state 呢,我們添加一個 `<script>` 標簽來把 `initialState` 賦給 `window.__INITIAL_STATE__`。 客戶端可以通過 `window.__INITIAL_STATE__` 獲取 `initialState`。 同時使用 script 標簽來引入打包后的 js bundle 文件。之前引入的 `serve-static` middleware 會處理它的請求。下面是代碼。 ~~~ function renderFullPage(html, initialState) { return ` <!doctype html> <html> <head> <title>Redux Universal Example</title> </head> <body> <div id="app">${html}</div> <script> window.__INITIAL_STATE__ = ${JSON.stringify(initialState)}; </script> <script src="/bundle.js"></script> </body> </html> `; } ~~~ > ##### 字符串插值語法須知 > 上面的示例使用了 ES6 的[模板字符串](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/template_strings)語法。它支持多行字符串和字符串插補特性,但需要支持 ES6。如果要在 Node 端使用 ES6,參考 [Babel require hook](https://babeljs.io/docs/usage/require/) 文檔。你也可以繼續使用 ES5。 ### 客戶端開發 客戶端代碼非常直觀。只需要從 `window.__INITIAL_STATE__` 得到初始 state,并傳給 [`createStore()`](#) 函數即可。 代碼如下: #### `client.js` ~~~ import React from 'react'; import { createStore } from 'redux'; import { Provider } from 'react-redux'; import App from './containers/App'; import counterApp from './reducers'; // 通過服務端注入的全局變量得到初始 state const initialState = window.__INITIAL_STATE__; // 使用初始 state 創建 Redux store const store = createStore(counterApp, initialState); React.render( <Provider store={store}> {() => <App />} </Provider>, document.getElementById('root') ); ~~~ 你可以選擇自己喜歡的打包工具(Webpack, Browserify 或其它)來編譯并打包文件到 `dist/bundle.js`。 當頁面加載時,打包后的 js 會啟動,并調用 [`React.render()`](https://facebook.github.io/react/docs/top-level-api.html#react.render),然后會與服務端渲染的 HTML 的 `data-react-id` 屬性做關聯。這會把新生成的 React 實例與服務端的虛擬 DOM 連接起來。因為同樣使用了來自 Redux store 的初始 state,并且 view 組件代碼是一樣的,結果就是我們得到了相同的 DOM。 就是這樣!這就是實現服務端渲染的所有步驟。 但這樣做還是比較原始的。只會用動態代碼渲染一個靜態的 View。下一步要做的是動態創建初始 state 支持動態渲染 view。 ### 準備初始 State 因為客戶端只是執行收到的代碼,剛開始的初始 state 可能是空的,然后根據需要獲取 state。在服務端,渲染是同步執行的而且我們只有一次渲染 view 的機會。在收到請求時,可能需要根據請求參數或者外部 state(如訪問 API 或者數據庫),計算后得到初始 state。 ### 處理 Request 參數 服務端收到的惟一輸入是來自瀏覽器的請求。在服務器啟動時可能需要做一些配置(如運行在開發環境還是生產環境),但這些配置是靜態的。 請求會包含 URL 請求相關信息,包括請求參數,它們對于做 [React Router](https://github.com/rackt/react-router) 路由時可能會有用。也可能在請求頭里包含 cookies,鑒權信息或者 POST 內容數據。下面演示如何基于請求參數來得到初始 state。 #### `server.js` ~~~ import qs from 'qs'; // 添加到文件開頭 function handleRender(req, res) { // 如果存在的話,從 request 讀取 counter const params = qs.parse(req.query); const counter = parseInt(params.counter) || 0; // 得到初始 state let initialState = { counter }; // 創建新的 Redux store 實例 const store = createStore(counterApp, initialState); // 把組件渲染成字符串 const html = React.renderToString( <Provider store={store}> {() => <App />} </Provider> ); // 從 Redux store 得到初始 state const finalState = store.getState(); // 把渲染后的頁面發給客戶端 res.send(renderFullPage(html, finalState)); } ~~~ 上面的代碼首先訪問 Express 的 `Request` 對象。把參數轉成數字,然后設置到初始 state 中。如果你在瀏覽器中訪問 [http://localhost:3000/?counter=100](http://localhost:3000/?counter=100),你會看到計數器從 100 開始。在渲染后的 HTML 中,你會看到計數顯示 100 同時設置進了 `__INITIAL_STATE__` 變量。 ### 獲取異步 State 服務端渲染常用的場景是處理異步 state。因為服務端渲染天生是同步的,因此異步的數據獲取操作對應到同步操作非常重要。 最簡單的做法是往同步代碼里傳遞一些回調函數。在這個回調函數里引用響應對象,把渲染后的 HTML 發給客戶端。不要擔心,并沒有想像中那么難。 本例中,我們假設有一個外部數據源提供計算器的初始值(所謂的把計算作為一種服務)。我們會模擬一個請求并使用結果創建初始 state。API 請求代碼如下: #### `api/counter.js` ~~~ function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min)) + min; } export function fetchCounter(callback) { setTimeout(() => { callback(getRandomInt(1, 100)); }, 500); } ~~~ 再次說明一下,這只是一個模擬的 API,我們使用 `setTimeout` 模擬一個需要 500 毫秒的請求(實現項目中 API 請求一般會更快)。傳入一個回調函數,它異步返回一個隨機數字。如果你使用了基于 Promise 的 API 工具,那么要把回調函數放到 `then` 中。 在服務端,把代碼使用 `fetchCounter` 包起來,在回調函數里拿到結果: #### `server.js` ~~~ // Add this to our imports import { fetchCounter } from './api/counter'; function handleRender(req, res) { // 異步請求模擬的 API fetchCounter(apiResult => { // 如果存在的話,從 request 讀取 counter const params = qs.parse(req.query); const counter = parseInt(params.counter) || apiResult || 0; // 得到初始 state let initialState = { counter }; // 創建新的 Redux store 實例 const store = createStore(counterApp, initialState); // 把組件渲染成字符串 const html = React.renderToString( <Provider store={store}> {() => <App />} </Provider> ); // 從 Redux store 得到初始 state const finalState = store.getState(); // 把渲染后的頁面發給客戶端 res.send(renderFullPage(html, finalState)); }); } ~~~ 因為在回調中使用了 `res.send()`,服務器會保護連接打開并在回調函數執行前不發送任何數據。你會發現每個請求都有 500ms 的延時。更高級的用法會包括對 API 請求出錯進行處理,比如錯誤的請求或者超時。 ### 安全注意事項 因為我們代碼中很多是基于用戶生成內容(UGC)和輸入的,不知不覺中,提高了應用可能受攻擊區域。任何應用都應該對用戶輸入做安全處理以避免跨站腳本攻擊(XSS)或者代碼注入。 我們的示例中,只對安全做基本處理。當從請求中拿參數時,對 `counter` 參數使用 `parseInt` 把它轉成數字。如果不這樣做,當 request 中有 script 標簽時,很容易在渲染的 HTML 中生成危險代碼。就像這樣的:`?counter=</script><script>doSomethingBad();</script>` 在我們極簡的示例中,把輸入轉成數字已經比較安全。如果處理更復雜的輸入,比如自定義格式的文本,你應該用安全函數處理輸入,比如 [validator.js](https://www.npmjs.com/package/validator)。 此外,可能添加額外的安全層來對產生的 state 進行消毒。`JSON.stringify` 可能會造成 script 注入。鑒于此,你需要清洗 JSON 字符串中的 HTML 標簽和其它危險的字符。可能通過字符串替換或者使用復雜的庫如 [serialize-javascript](https://github.com/yahoo/serialize-javascript) 處理。 ### 下一步 你還可以參考 [異步 Actions](#) 學習更多使用 Promise 和 thunk 這些異步元素來表示異步數據流的方法。記住,那里學到的任何內容都可以用于同構渲染。 如果你使用了 [React Router](https://github.com/rackt/react-router),你可能還需要在路由處理組件中使用靜態的 `fetchData()` 方法來獲取依賴的數據。它可能返回 [異步 action](#),以便你的 `handleRender` 函數可以匹配到對應的組件類,對它們均 dispatch `fetchData()` 的結果,在 Promise 解決后才渲染。這樣不同路由需要調用的 API 請求都并置于路由處理組件了。在客戶端,你也可以使用同樣技術來避免在切換頁面時,當數據還沒有加載完成前執行路由。(Revision needed)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看