ホームページ > 記事 > ウェブフロントエンド > React サーバーサイド レンダリングを効率的に使用する方法
今回は、React サーバーサイド レンダリングを効率的に使用する方法と、React サーバーサイド レンダリングを使用する際の 注意事項 を説明します。実際のケースを見てみましょう。
React は、コンポーネント (仮想 DOM) を HTML ブラウザ環境への依存を解決することに加えて、サーバーサイドレンダリングでは次の 2 つの問題も解決する必要があります:Redux
Redux は、Flux と同様の一方向のデータ フローのセットを提供し、アプリケーション全体で 1 つのストアのみを維持し、その機能指向の機能によりサーバー側のレンダリングのサポートが容易になります。Redux の仕組みを学ぶには 2 分
ストアについて:react-routerreact-router は、宣言的な方法でさまざまなルーティング決定を照合して、ページ上にさまざまなコンポーネントを表示し、props を介してコンポーネントにルーティング情報を渡します。そのため、ルーティングが変更される限り、propsこれによりコンポーネントが変更され、コンポーネントの再レンダリングがトリガーされます。
リスト ページ /list と詳細ページ /item/:id の 2 ページだけを持つ非常に単純なアプリケーションがあるとします。リスト上の項目をクリックして詳細ページに入ります。
次のようなルートを定義できます
、./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.jsimport 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全体の状態ツリーです状態ツリーの下の各フィールドは独自のリデューサーを持つこともできるため、ここでは listReducer と itemReducer が導入されています。これら 2 つのリデューサーの状態パラメーターは、状態ツリー全体の対応するリスト フィールドと項目フィールドにすぎないことがわかります。 具体的には、 ./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 は項目を含む単純な配列であり、次の構造に似ています: [{ id: 0, name: 'first item'}, {id: 1, name : '2 番目の項目'}]、'FETCH_LIST_SUCCESS' の action.payload から取得されます。 次に、./reducers/item.jsで取得したアイテムデータを加工します
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 中国語 Web サイトの他の関連記事を参照してください。