今回は、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;
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) }; }
const initialState = []; export default function listReducer(state = initialState, action) { switch(action.type) { case 'FETCH_LIST_SUCCESS': return [...action.payload]; default: return state; } }
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 サイトの他の関連記事を参照してください。