React サーバーサイドレンダリング原則の分析と実践

React サーバーサイドレンダリング原則の分析と実践

ほとんどの人は、サーバーサイド レンダリング (SSR と呼んでいます) の概念について聞いたことがあるでしょう。多くの学生は、すでに会社でサーバーサイド レンダリング プロジェクトを実施したことがあるかもしれません。Vue や React 開発プロジェクトなどの主流のシングルページ アプリケーションでは、通常、クライアントサイド レンダリング モデル (CSR と呼んでいます) が使用されます。

しかし、このモデルには2つの明らかな問題があります。1つ目は、TTFP時間が比較的長いことです。TTFPは最初の画面表示時間を指します。同時に、SEOランキングの条件を満たしておらず、検索エンジンでのランキングはあまり良くありません。したがって、いくつかのツールを使用してプロジェクトを改善し、シングルページ アプリケーションをサーバー側レンダリング プロジェクトに変換することで、これらの問題を解決することができます。

現在主流のサーバー側レンダリング フレームワーク (SSR フレームワークとも呼ばれます) は、Vue の場合は Nuxt.js、React の場合は Next.js です。ここでは、これらの SSR フレームワークは使用しませんが、その基本原則を理解するために、完全な SSR フレームワークをゼロから構築します。

サーバー上でReactコンポーネントを書く

クライアント側レンダリングの場合、まずブラウザがブラウザにリクエストを送信し、サーバーがページの html ファイルを返します。次に html が再度サーバーにリクエストを送信し、サーバーが js ファイルを返し、js ファイルがブラウザで実行されてページ構造が描画され、ブラウザにレンダリングされてページのレンダリングが完了します。

サーバー側レンダリングの場合は、プロセスが異なります。ブラウザがリクエストを送信し、サーバーが React コードを実行してページを生成し、サーバーが生成されたページをブラウザに返してレンダリングします。この場合、React コードはフロントエンドではなくサーバーの一部になります。

ここでコードを示します。まず、npm init でプロジェクトを初期化し、次に react、express、webpack、webpack-cli、webpack-node-externals をインストールする必要があります。

まず、React コンポーネントを記述します。 .src/components/Home/index.js では、js は node 環境で実行されるため、CommonJS 仕様に従い、インポートとエクスポートに require と module.exports を使用する必要があります。

React は、次のコードで定義されます。

const ホーム = () => {
  <div>ホーム</div>に戻る
}

モジュール.エクスポート = {
  デフォルト: ホーム
};

ここで開発した Home コンポーネントは、Node で直接実行することはできません。Node.js が認識できるように、webpack ツールを使用して jsx 構文を js 構文にパッケージ化してコンパイルする必要があります。webpack.server.js ファイルを作成する必要があります。

サーバー側で webpack を使用する場合は、ターゲットをノードとしてキーと値のペアを追加する必要があります。パスがサーバー側で使用される場合、js にパッケージ化する必要がないことはわかっています。パスがブラウザ側で使用される場合、js にパッケージ化する必要があります。したがって、サーバー側とブラウザ側でコンパイルする必要がある js は完全に異なります。したがって、パッケージ化するときに、サーバー側のコードとブラウザ側のコードのどちらをパッケージ化するかを webpack に指示する必要があります。

エントリ ファイルは、ノードの起動ファイルです。ここでは、./src/index.js と記述します。出力ファイルの名前は bundle で、ディレクトリは次のディレクトリの build フォルダーにあります。

定数Path = require('path');
const NodeExternals = require('webpack-node-externals'); // サーバー上で webpack を実行するには、NodeExternals を実行する必要があります。これにより、express などのノード モジュールが js にパッケージ化されなくなります。

モジュール.エクスポート = {
  ターゲット: 'ノード',
  モード: '開発'、
  エントリ: './src/server/index.js',
  出力: {
    ファイル名: 'bundle.js',
    パス: Path.resolve(__dirname, 'build')
  },
  外部: [NodeExternals()],
  モジュール: {
    ルール:
      {
        テスト: /.js?$/,
        ローダー: 'babel-loader',
        除外: /node_modules/、
        オプション:
          プリセット: ['react', 'stage-0', ['env', {
            ターゲット: {
              ブラウザ: ['最後の 2 つのバージョン']
            }
          }]]
        }
      }
    ]
  }
}

依存モジュールをインストールする

npm で babel-loader と babel-core をインストールし、 babel-preset-react と babel-preset-stage-0 と babel-preset-env --save を実行します。

次に、express モジュールに基づいて簡単なサービスを作成します。 ./src/server/index.js

var express = require('express');
var app = express();
const Home = require('../Components/Home');
app.get('*', 関数(req, res) {
  res.send(`<h1>hello</h1>`);
})

var server = app.listen(3000);

実行するには、webpack.server.js 構成ファイルを使用して webpack を実行します。

webpack --config webpack.server.js

パッケージ化後、ディレクトリに bundle.js が表示されます。この js は、パッケージ化によって生成された最終的な実行可能コードです。ノードを使用してこのファイルを実行し、ポート 3000 でサーバーを起動できます。このサービスにアクセスするには、127.0.0.1:3000 にアクセスし、ブラウザに Hello と出力されていることを確認します。

ノード ./build/bundile.js

上記のコードは実行前に webpack を使用してコンパイルされるため、ES モジュール仕様がサポートされ、CommonJS の使用が強制されなくなります。

src/components/Home/index.js

'react' から React をインポートします。

const ホーム = () => {
  <div>ホーム</div>に戻る
}

デフォルトのホームをエクスポートします。

/src/server/index.js の Home コンポーネントを使用できます。ここでは、まず react-dom をインストールし、renderToString を使用して Home コンポーネントをラベル文字列に変換する必要があります。もちろん、ここでは React に依存する必要があるため、React を導入する必要があります。

'express' から express をインポートします。
'../Components/Home' から Home をインポートします。
'react' から React をインポートします。
'react-dom/server' から renderToString をインポートします。

express() は、定数です。
const コンテンツ = renderToString(<Home />);
app.get('*', 関数(req, res) {
  res.send(`
    <html>
      <body>${コンテンツ}</body>
    </html>
  `);
})

var server = app.listen(3000);

# webpack を再パッケージ化 --config webpack.server.js
# サービスノード ./build/bundile.js を実行します

この時点で、ページには React コンポーネントのコードが表示されます。

React のサーバー側レンダリングは仮想 DOM に基づいており、サーバー側レンダリングによりページの最初の画面レンダリングが大幅に高速化されます。ただし、サーバーサイドレンダリングにもデメリットがあります。クライアントサイドレンダリングではReactコードがブラウザ側で実行されるため、ユーザーのブラウザ側のパフォーマンスを消費しますが、サーバーサイドレンダリングではReactコードがサーバー上で実行されるため、サーバー側のパフォーマンスを消費します。 React コードは多くのコンピューティング パフォーマンスを消費するため、サーバーのパフォーマンスが大幅に消費されます。

プロジェクトで SEO 最適化を使用する必要がなく、プロジェクトのアクセス速度がすでに非常に速い場合は、コストが依然として比較的高いため、SSR テクノロジを使用しないことをお勧めします。

上記のコードを変更するたびに、webpack のパッケージングを再実行してサーバーを起動する必要がありますが、これはデバッグするには面倒すぎます。この問題を解決するには、webpack の自動パッケージングとノードの再起動を行う必要があります。 package.json にビルド コマンドを追加し、--watch を使用して自動パッケージ化のファイルの変更を監視します。

{
  ...
  「スクリプト」: {
    "ビルド": "webpack --config webpack.server.js --watch"
  }
  ...
}

再パッケージ化だけでは不十分で、ノード サーバーを再起動する必要もあります。ここでは、nodemon モジュールを使用する必要があります。ここでは、nodemon のグローバル インストールを使用し、package.json ファイルに start コマンドを追加してノード サーバーを起動します。 nodemon を使用してビルド ファイルを監視し、変更があった場合は "node ./build/bundile.js" を再実行します。ここでは二重引用符を保持し、翻訳するだけです。

{
  ...
  「スクリプト」: {
    "開始": "nodemon --watch ビルド --exec node \"./build/bundile.js\"",
    "ビルド": "webpack --config webpack.server.js --watch"
  }
  ...
}

この時点でサーバーを起動します。ビルド後は他のコマンドは許可されないため、ここでは 2 つのウィンドウで次のコマンドを実行する必要があります。

npm 実行ビルド
npm 実行開始

この時点で、コードを変更すると、ページは自動的に更新されます。

しかし、上記のプロセスはまだ少し面倒です。コマンドを実行するには 2 つのウィンドウが必要です。両方のコマンドを 1 つのウィンドウで実行したいのです。グローバルにインストールできるサードパーティ モジュール npm-run-all を使用する必要があります。次に、package.json で変更します。

開発環境でパッケージ化とデバッグを行う必要があります。dev コマンドを作成し、その中で npm-run-all を実行します。--parallel は並列実行を意味し、dev: で始まるすべてのコマンドを実行します。開始とビルドの前に dev: を追加します。この時点で、サーバーを起動してファイルの変更をリッスンしたい場合は、npm run dev を実行するだけです。

{
  ...
  「スクリプト」: {
    "dev": "npm-run-all --parallel dev:**",
    "dev:start": "nodemon --watch ビルド --exec node \"./build/bundile.js\"",
    "dev:build": "webpack --config webpack.server.js --watch"
  }
  ...
}

同型性とは何ですか?

たとえば、次のコードでは、クリック時にクリックプロンプトがポップアップ表示されるように、クリックイベントを div にバインドします。しかし、実行後、サーバーがイベントをバインドできないため、このイベントはバインドされていないことがわかります。

src/components/Home/index.js

'react' から React をインポートします。

const ホーム = () => {
  <div onClick={() => { alert('click'); }}>ホーム</div> を返します。
}

デフォルトのホームをエクスポートします。

通常は、まずページをレンダリングし、次に従来の React プロジェクトと同様にブラウザ側で同じコードを実行して、クリック イベントが利用できるようにします。

これは同型性の概念につながります。私の理解では、React コードのセットはサーバー上で一度実行され、クライアント上で再度実行されます。

Isomorphism は無効なクリック イベントの問題を解決できます。まず、サーバーは 1 回実行することでページを正常に表示でき、クライアントは再度実行することでイベントをバインドできます。

ページがレンダリングされるときに index.js をロードし、app.use を使用して静的ファイルへのアクセス パスを作成すると、アクセスされた index.js が /public/index.js ファイルに要求されます。

app.use(express.static('public'));

app.get('/', 関数(req, res) {
  res.send(`
    <html>
      <本文>
        <div id="root">${コンテンツ}</div>
        <script src="/index.js"></script>
      </本文>
    </html>
  `);
})

公開/index.js

コンソールログ('public');

この状況に基づいて、ブラウザで React コードを 1 回実行できます。ここで新しい /src/client/index.js を作成します。クライアントによって実行されたコードを貼り付けます。ここでは、同型コードで render の代わりに hydrate を使用します。

'react' から React をインポートします。
'react-dom' から ReactDOM をインポートします。

'../Components/Home' から Home をインポートします。

ReactDOM.hydrate(<Home />, document.getElementById('root'));

次に、ルート ディレクトリに webpack.client.js ファイルを作成する必要があります。エントリファイルは./src/client/index.js、エクスポートファイルはpublic/index.jsです。

定数Path = require('path');

モジュール.エクスポート = {
  モード: '開発'、
  エントリ: './src/client/index.js',
  出力: {
    ファイル名: 'index.js',
    パス: Path.resolve(__dirname, 'public')
  },
  モジュール: {
    ルール:
      {
        テスト: /.js?$/,
        ローダー: 'babel-loader',
        除外: /node_modules/、
        オプション:
          プリセット: ['react', 'stage-0', ['env', {
            ターゲット: {
              ブラウザ: ['最後の 2 つのバージョン']
            }
          }]]
        }
      }
    ]
  }
}

package.jsonファイルにクライアントディレクトリをパッケージ化するコマンドを追加します。

{
  ...
  「スクリプト」: {
    "dev": "npm-run-all --parallel dev:**",
    "dev:start": "nodemon --watch ビルド --exec node \"./build/bundile.js\"",
    "dev:build": "webpack --config webpack.server.js --watch",
    "dev:build": "webpack --config webpack.client.js --watch",
  }
  ...
}

この方法では、クライアントの起動時に実行されるファイルをコンパイルします。ページに再度アクセスすると、イベントをバインドできます。

次に、上記プロジェクトのコードを整理します。上記の webpack.server.js ファイルと webpack.client.js ファイルには重複が多くあります。webpack-merge プラグインを使用してコンテンツをマージできます。

webpack.base.js

モジュール.エクスポート = {
  モジュール: {
    ルール:
      {
        テスト: /.js?$/,
        ローダー: 'babel-loader',
        除外: /node_modules/、
        オプション:
          プリセット: ['react', 'stage-0', ['env', {
            ターゲット: {
              ブラウザ: ['最後の 2 つのバージョン']
            }
          }]]
        }
      }
    ]
  }
}

webpack.server.js

定数Path = require('path');
const NodeExternals = require('webpack-node-externals'); // サーバー上で webpack を実行するには、NodeExternals を実行する必要があります。これにより、express などのノード モジュールが js にパッケージ化されなくなります。

'webpack-merge' を require してマージします。
config は './webpack.base.js' を必要とします。

定数serverConfig = {
  ターゲット: 'ノード',
  モード: '開発'、
  エントリ: './src/server/index.js',
  出力: {
    ファイル名: 'bundle.js',
    パス: Path.resolve(__dirname, 'build')
  },
  外部: [NodeExternals()],
}

module.exports = merge(config, serverConfig);

webpack.client.js

定数Path = require('path');
'webpack-merge' を require してマージします。
config は './webpack.base.js' を必要とします。

定数クライアント設定 = {
  モード: '開発'、
  エントリ: './src/client/index.js',
  出力: {
    ファイル名: 'index.js',
    パス: Path.resolve(__dirname, 'public')
  }
};

module.exports = merge(config, clientConfig);

サーバー上で実行されるコードは src/server に配置され、ブラウザ上で実行される js は src/client に配置されます。

React サーバーサイドレンダリングの分析と実践に関するこの記事はこれで終わりです。React サーバーサイドレンダリングに関するより関連性の高いコンテンツについては、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き閲覧してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Reactのサーバーサイドレンダリング(同型性)の手法を詳しく解説
  • Reactサーバーサイドレンダリング入門から習得まで徹底解説
  • Reactサーバーサイドレンダリングに最適なソリューションの詳細な説明
  • Node を使用して reactSSR サーバーサイド レンダリング アーキテクチャを構築する
  • サーバー上でのReactレンダリングの実装の詳細な説明
  • React サーバーサイドレンダリング (概要)
  • React サーバーサイドレンダリングと同型実装

<<:  CentOS6.8 は cmake を使用して MySQL5.7.18 をインストールします。

>>:  Kali Linux Vmware 仮想マシンのインストール (図とテキスト)

推薦する

MySQL が起動直後にシャットダウンする問題 (ibdata1 ファイルの破損が原因) に対する完璧な解決策

コンピュータ ルームのサーバー上の mysql がしばらく実行されていたのですが、突然、再起動しても...

HTML のタイトル、段落、改行、水平線、特殊文字についての簡単な説明

タイトルXML/HTML コードコンテンツをクリップボードにコピー< h1 >第 1 レ...

Vue3はフロントエンドのログを出力するためにaxiosインターセプターを使用する

目次1. はじめに2. axiosインターセプターを使用してフロントエンドログを出力する1. はじめ...

webpack イメージを base64 に変換する例

url-loader をダウンロード 糸を追加 -D URLローダー モジュール: { ルール: {...

Webフロントエンド開発経験の概要

XMLファイルは、可能な限りutf-8でエンコードする必要があります。gb2312には、?など、保存...

Vuexはセッションストレージデータを結合して、ページを更新するときにデータが失われる問題を解決します

目次序文1. 理由: 2. 解決策のアイデア: 1. ローカル保存方法: 2. 実装手順: 3. 最...

CSSアニメーションを途中で止めて姿勢を維持する方法

序文かつて、難しい問題に遭遇しました。タワークレーンからスイングハウスを落下させる必要がありましたが...

パーソナライズされた検索エンジンを使用して、必要なパーソナライズされた情報を検索します

現在、多くの人がインターネット上で生活しており、インターネットで情報を検索することは日常的な作業とな...

HTML タグ tbody の使い方と説明

tbody 要素は、thead 要素および tfoot 要素と組み合わせて使用​​する必要があります...

レスポンシブWebデザイン学習(1) - 画面サイズと使用率の決定

最近では、モバイルデバイスがますます普及しており、ユーザーがスマートフォンやタブレットを使用して W...

Linux で PHP curl 拡張機能をインストールする方法の詳細な説明

この記事では、Linux で PHP curl 拡張機能をインストールする方法について説明します。ご...

MySQL の挿入ステートメントの使用実体験

目次1. 挿入のいくつかの構文1-1. 通常の挿入文1-2. 挿入または更新1-3. 挿入または交換...

Sublime Text - ブラウザのショートカットキーを設定するための推奨方法

コード効果を異なるブラウザで表示することはよくあることなので、異なるショートカットキーを使用して対応...

MySQL が大規模トランザクションを避けるべき理由とその解決方法

何が大問題ですか?長時間実行され、長時間コミットされないトランザクションは、大規模トランザクションと...

Linux での umask の使用に関する詳細な説明

私は最近 Linux を学び始めました。Ma Ge の umask に関する Linux コースを読...