這次帶給大家怎樣高效的使用React伺服器端渲染,使用React伺服器端渲染的注意事項有哪些,下面就是實戰案例,一起來看一下。
React 提供了兩個方法renderToString 和renderToStaticMarkup 用來將元件(Virtual DOM)輸出成HTML 字串,這是React 伺服器端渲染的基礎,它移除了伺服器端對於瀏覽器環境的依賴,所以讓伺服器端渲染變成了一件有吸引力的事情。
伺服器端渲染除了要解決對瀏覽器環境的依賴,還要解決兩個問題:
前後端可以共享程式碼
#前後端路由可以統一處理
React 生態提供了許多選擇方案,這裡我們選用Redux 和react-router 來做說明。
Redux
Redux 提供了一套類似Flux 的單向資料流,整個應用只維護一個Store,以及面向函數式的特性讓它對伺服器端渲染支援很友善。
2 分鐘了解Redux 是如何運作的
#關於Store:
整個應用程式只有一個唯一的Store
Store 對應的狀態樹(State),由呼叫一個reducer 函數(root reducer)產生
react-router
react-router
react-router 透過聲明式的方式來匹配不同路由決定在頁面上展示不同的元件,並且透過props 將路由資訊傳遞給元件使用,所以只要路由變更,props 就會變化,觸發元件re-render。 假設有一個很簡單的應用,只有兩個頁面,一個列表頁 /list 和一個詳情頁 /item/:id,點擊列表上的條目進入詳情頁。
可以這樣
定義路由,./routes.jsimport React from 'react';
import { Route } from 'react-router';
import { List, Item } from './components';
// 无状态(stateless)组件,一个简单的容器,react-router 会根据 route
// 规则匹配到的组件作为 `props.children` 传入
const Container = (props) => {
return (
<p>{props.children}</p>
);
};
// route 规则:
// - `/list` 显示 `List` 组件
// - `/item/:id` 显示 `Item` 组件
const routes = (
<Route path="/" component={Container} >
<Route path="list" component={List} />
<Route path="item/:id" component={Item} />
</Route>
);
export default routes;
從這裡開始,我們透過這個非常簡單的應用程式來解釋實作伺服器端渲染前後端涉及的一些細節問題。
Reducer
Store 是由reducer 產生的,所以reducer 實際上反映了Store 的狀態樹結構
#. /reducers/index.js
import listReducer from './list'; import itemReducer from './item'; export default function rootReducer(state = {}, action) { return { list: listReducer(state.list, action), item: itemReducer(state.item, action) }; }
rootReducer 的state 參數就是整個Store 的狀態樹,狀態樹下的每個欄位對應也可以有自己的reducer,所以這裡引進了listReducer 和itemReducer,可以看到這兩個reducer的state 參數就只是整個狀態樹上對應的list 和item 欄位。
具體到./reducers/list.js
const initialState = []; export default function listReducer(state = initialState, action) { switch(action.type) { case 'FETCH_LIST_SUCCESS': return [...action.payload]; default: return state; } }###list 就是一個包含items 的簡單數組,可能類似這個結構:[{ id: 0, name: 'first item'} , {id: 1, name: 'second item'}],從'FETCH_LIST_SUCCESS' 的action.payload 取得。 ######接著是 ./reducers/item.js,處理取得到的 item 資料###
const initialState = {}; export default function listReducer(state = initialState, action) { switch(action.type) { case 'FETCH_ITEM_SUCCESS': return [...action.payload]; default: return state; } }
Action
对应的应该要有两个 action 来获取 list 和 item,触发 reducer 更改 Store,这里我们定义 fetchList 和 fetchItem 两个 action。
./actions/index.js
import fetch from 'isomorphic-fetch'; export function fetchList() { return (dispatch) => { return fetch('/api/list') .then(res => res.json()) .then(json => dispatch({ type: 'FETCH_LIST_SUCCESS', payload: json })); } } export function fetchItem(id) { return (dispatch) => { if (!id) return Promise.resolve(); return fetch(`/api/item/${id}`) .then(res => res.json()) .then(json => dispatch({ type: 'FETCH_ITEM_SUCCESS', payload: json })); } }
isomorphic-fetch 是一个前后端通用的 Ajax 实现,前后端要共享代码这点很重要。
另外因为涉及到异步请求,这里的 action 用到了 thunk,也就是函数,redux 通过 thunk-middleware 来处理这类 action,把函数当作普通的 action dispatch 就好了,比如 dispatch(fetchList())
Store
我们用一个独立的 ./store.js,配置(比如 Apply Middleware)生成 Store
import { createStore } from 'redux'; import rootReducer from './reducers'; // Apply middleware here // ... export default function configureStore(initialState) { const store = createStore(rootReducer, initialState); return store; }
react-redux
接下来实现 ,
./app.js
import React from 'react'; import { render } from 'react-dom'; import { Router } from 'react-router'; import createBrowserHistory from 'history/lib/createBrowserHistory'; import { Provider } from 'react-redux'; import routes from './routes'; import configureStore from './store'; // `INITIAL_STATE` 来自服务器端渲染,下一部分细说 const initialState = window.INITIAL_STATE; const store = configureStore(initialState); const Root = (props) => { return ( <p> <Provider store={store}> <Router history={createBrowserHistory()}> {routes} </Router> </Provider> </p> ); } render(<Root />, document.getElementById('root'));
至此,客户端部分结束。
Server Rendering
接下来的服务器端就比较简单了,获取数据可以调用 action,routes 在服务器端的处理参考 react-router server rendering,在服务器端用一个 match 方法将拿到的 request url 匹配到我们之前定义的 routes,解析成和客户端一致的 props 对象传递给组件。
./server.js
import express from 'express'; import React from 'react'; import { renderToString } from 'react-dom/server'; import { RoutingContext, match } from 'react-router'; import { Provider } from 'react-redux'; import routes from './routes'; import configureStore from './store'; const app = express(); function renderFullPage(html, initialState) { return ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> </head> <body> <p id="root"> <p> ${html} </p> </p> <script> window.INITIAL_STATE = ${JSON.stringify(initialState)}; </script> <script src="/static/bundle.js"></script> </body> </html> `; } app.use((req, res) => { match({ routes, location: req.url }, (err, redirectLocation, renderProps) => { if (err) { res.status(500).end(`Internal Server Error ${err}`); } else if (redirectLocation) { res.redirect(redirectLocation.pathname + redirectLocation.search); } else if (renderProps) { const store = configureStore(); const state = store.getState(); Promise.all([ store.dispatch(fetchList()), store.dispatch(fetchItem(renderProps.params.id)) ]) .then(() => { const html = renderToString( <Provider store={store}> <RoutingContext {...renderProps} /> </Provider> ); res.end(renderFullPage(html, store.getState())); }); } else { res.status(404).end('Not found'); } }); });
服务器端渲染部分可以直接通过共用客户端 store.dispatch(action) 来统一获取 Store 数据。另外注意 renderFullPage 生成的页面 HTML 在 React 组件 mount 的部分(
),前后端的 HTML 结构应该是一致的。然后要把 store 的状态树写入一个全局变量(INITIAL_STATE),这样客户端初始化 render 的时候能够校验服务器生成的 HTML 结构,并且同步到初始化状态,然后整个页面被客户端接管。
最后关于页面内链接跳转如何处理?
react-router 提供了一个 组件用来替代 标签,它负责管理浏览器 history,从而不是每次点击链接都去请求服务器,然后可以通过绑定 onClick 事件来作其他处理。
比如在 /list 页面,对于每一个 item 都会用 绑定一个 route url:/item/:id,并且绑定 onClick 去触发 dispatch(fetchItem(id)) 获取数据,显示详情页内容。
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
vue axios生产环境与发布环境配置不同接口地址步骤详解
以上是怎樣高效的使用React伺服器端渲染的詳細內容。更多資訊請關注PHP中文網其他相關文章!