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 仮想マシンのインストール (図とテキスト)

推薦する

TypeScript 列挙型

目次1. 概要2. デジタル列挙2.1 逆マッピング3. 文字列の列挙4. const列挙5. まと...

DockerはRedis5.0をビルドし、データをマウントします

目次1. 永続データの簡単なマウント2. DockerFileでイメージをビルドし、設定ファイルを指...

Linux での透過的巨大ページの使用と無効化の概要

導入コンピューティングのニーズが拡大し続けるにつれて、アプリケーションのメモリに対する需要も増加して...

MySQL 5.7.17無料インストール版のインストールと設定

MYSQLバージョン:MySQL Community Server 5.7.17、インストール不要版...

効率をN倍に高めるVimクイックリファレンステーブル15個

昨年の前半から開発と娯楽のために Linux を使い始めましたが、今では Windows には戻れま...

HTML テーブル マークアップ チュートリアル (16): タイトルの水平方向の配置属性 ALIGN

デフォルトでは、表のタイトルは水平方向に中央揃えされます。ALIGN 属性を使用して、タイトル テキ...

サーバーストレステストの概念と方法 (TPS/同時実行性)

目次1 ストレステストの指標1.1 秒あたり1.2 クォータ1.3 平均処理時間(RT) 1.4 同...

Linux (Ubuntu) での MySQL 5.6.28 のインストールと設定のチュートリアル

mysql5.6.28のインストールと設定方法1. 基本的なシステム情報を確認し、yumでインストー...

本番環境でのMySQLパラメータsql_safe_updatesの使用に関する詳細な説明

序文アプリケーションのバグや DBA の誤操作が発生した場合、テーブル全体が更新される可能性がありま...

docker compose を使ってワンクリックで分散構成センター Apollo を展開するプロセスの詳細な説明

導入分散について話すときは、分散構成センター、分散ログ、分散リンク トラッキングなどについて考える必...

Tomcat および Web アプリケーションの Docker デプロイメントの実装

1.dockerをオンラインでダウンロードする yum インストール -y epel-release...

MySQL における int の最大値の詳細な説明

導入2日前に見た問題について詳細に書きます。バイトコンピューターがバイナリに基づいていることは誰もが...

C++ を使用して MySQL に接続する方法

C++でMySQLに接続する際の参考情報です。具体的な内容は以下のとおりです。 MySQLCon ク...

JS でオブジェクト プロパティを簡単にトラバースするいくつかの方法

目次1. 自己列挙可能なプロパティ2. Object.values()はプロパティ値を返します3. ...

MySQL 接続数を設定する方法 (接続数が多すぎる)

mysql使用中に接続数が超過していることが判明しました~~~~ [root@linux-node...