React サーバーサイド レンダリングを効率的に使用する方法

php中世界最好的语言
リリース: 2018-05-23 10:58:47
オリジナル
1767 人が閲覧しました

今回は、React サーバーサイド レンダリングを効率的に使用する方法と、React サーバーサイド レンダリングを使用する際の 注意事項 を​​説明します。実際のケースを見てみましょう。

React は、コンポーネント (仮想 DOM) を HTML

String に出力するための 2 つのメソッド renderToString と renderToStaticMarkup を提供します。これは、React のサーバー側レンダリングの基礎となるため、サーバー側を作成します。側面が魅力的なものを演出します。

ブラウザ環境への依存を解決することに加えて、サーバーサイドレンダリングでは次の 2 つの問題も解決する必要があります:

  1. フロントエンドとバックエンドはコードを共有できる

  2. フロントエンドとバックエンドエンドルーティングは均一に処理できます

React エコシステムは多くのソリューションを提供します ここでは、例として Redux と React-router を選択します。

Redux

Redux は、Flux と同様の一方向のデータ フローのセットを提供し、アプリケーション全体で 1 つのストアのみを維持し、その機能指向の機能によりサーバー側のレンダリングのサポートが容易になります。

Redux の仕組みを学ぶには 2 分

ストアについて:

  1. アプリケーション全体には 1 つだけのストアがあります

  2. ストアの対応する状態ツリー (State) はリデューサーによって呼び出されます関数 (ルート リデューサー) が生成します

  3. ステート ツリーの各フィールドは、さまざまなリデューサー関数によってさらに生成できます

  4. Store には、データ フローを処理するためのディスパッチ、getState などのいくつかのメソッドが含まれています

  5. ストアのステート ツリーのみ

Reduxのデータフローへの変更はdispatch(action)によってトリガーできます:

  1. actionは{type, payload}を含むオブジェクトです

  2. reducer関数はstore.dispatch(action)によってトリガーされます

  3. reducer 関数は 2 つのパラメーター (状態、アクション) を受け取り、新しい状態を返します

  4. reducer 関数は action.type を決定し、対応する action.payload データを処理して

    状態ツリーを更新

  5. アプリケーション全体 たとえば、ストアは UI スナップショットに対応し、サーバー側のレンダリングは、サーバー側でストアを初期化し、ストアをアプリケーションのルート コンポーネントに渡し、ルート コンポーネントで renderToString を呼び出して、アプリケーション全体を初期化データを含む HTML に変換します。

react-routerreact-router は、宣言的な方法でさまざまなルーティング決定を照合して、ページ上にさまざまなコンポーネントを表示し、props を介してコンポーネントにルーティング情報を渡します。そのため、ルーティングが変更される限り、propsこれによりコンポーネントが変更され、コンポーネントの再レンダリングがトリガーされます。

リスト ページ /list と詳細ページ /item/:id の 2 ページだけを持つ非常に単純なアプリケーションがあるとします。リスト上の項目をクリックして詳細ページに入ります。

次のようなルートを定義できます

、./routes.js

import 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全体の状態ツリーです状態ツリーの下の各フィールドは独自のリデューサーを持つこともできるため、ここでは 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

接下来实现 组件,然后把 redux 和 react 组件关联起来,具体细节参见 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生产环境与发布环境配置不同接口地址步骤详解

JS计算圆周率到小数点后100位实现步骤详解

以上がReact サーバーサイド レンダリングを効率的に使用する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート