Reactはサーバー側のレンダリングをネイティブに実装します
記事の出典:Lagouの大きなフロントエンドの高給トレーニングキャンプ
1.サーバー側のレンダリングがすぐに開始されます
1. ReactSSRを実装する
- レンダリングするReactコンポーネントを紹介します
- renderToStringメソッドを使用してReactコンポーネントをHTML文字列に変換します
- 結果のHTML文字列をクライアントに応答します
renderToString
はじめに、HTML文字列を変換するためのReactアセンブリメソッドreact-dom/server
。
2.Webpackパッケージ構成
問題:ノード環境はESModuleモジュールシステムをサポートしておらず、JSX構文をサポートしていません
3.プロジェクト起動コマンドの構成
- サーバー側のパッケージ化コマンドを構成します。
"dev:server-build": "webpack --config webpack.server.js --watch"
- サーバー開始コマンドを構成します。
"dev:server-run": "nodemon --watch build --exec\"node build/bundler.js\""
2つのクライアント側React追加イベント
1.実現分析
クライアント側でコンポーネントの2次「レンダリング」を実行し、コンポーネント要素にイベントをアタッチします
2.クライアント側がハイドレートを「レンダリング」します
ハイドレートメソッドを使用してコンポーネントをレンダリングし、コンポーネント要素にイベントをアタッチします。
ハイドレートメソッドは、レンダリング時に既存のDOMノードを再利用し、ノードの再生成と元のDOMノードの削除のオーバーヘッドを削減します。
react-domを介して水和物をインポートする
ReactDOM.hydrate(<Home/>, document.getElementById('#root'))
3. ClientReactパッケージ構成
-
webpackの構成
パッケージ化の目的:JSX構文を変換し、ブラウザーで認識されない高度なJavaScript構文を変換します。
パッケージの宛先:パブリックフォルダー -
パッケージ起動コマンドの設定
"dev:client-build": "webpack --config webpack.client.js --watch"
4.クライアントパッケージファイルリクエストリンクを追加します
クライアントへの応答でHTMLコードにスクリプトタグを追加し、クライアントのJavaScriptにファイルをパッケージ化するように要求します。
<html>
<head>
<title> React SSR</title>
</head>
<body>
<div id="root">${content}</div>
<script src="bundle.js"></script>
</body>
</html>
5.静的リソースへのサーバー側アクセス
サーバー側のプログラムは静的リソースアクセス機能を実装し、クライアント側のJavaScriptパッケージファイルは静的リソースとして使用されます。
app.use(express.static('public'))
3、最適化
1.webpack構成をマージします
サーバー側のwebpack構成とクライアント側のwebpack構成が複製され、複製された構成がwebpack.base.js構成ファイルに抽象化されます。
2.プロジェクト開始コマンドのマージ
目的:npm-run-allツールを使用して、1つのコマンドを使用してプロジェクトを開始し、複数のコマンドを開始するという面倒な問題を解決します。
"dev": "npm-run-all --parallel dev:*"
3.サーバー側のパッケージファイルボリュームの最適化
問題:サーバー側のパッケージファイルにノードシステムモジュールが含まれているため、パッケージファイル自体が巨大になります。
解決策:webpack構成を使用して、パッケージファイル内のノードモジュールを削除します。
const nodeExternals = require('webpack-node-externals')
const config = {
externals: [nodeExternals()]
}
module.exports = merge(baseConfig, config)
4.スタートアップサーバーコードとレンダリングコードをモジュール式に分割する
コード編成を最適化し、Reactコンポーネントコードをレンダリングすることは独立した機能であるため、サーバー側のエントリファイルから抽出されます。
4、ルーティングサポート
1.実現分析
React SSRプロジェクトでは、両端でルーティングを実装する必要があります。
クライアント側ルーティングは、ユーザーがリンクをクリックしてページにジャンプできるようにするために使用されます。
サーバー側ルーティングは、ユーザーがブラウザーのアドレスバーから直接ページにアクセスできるようにするために使用されます。
クライアントとサーバーは、一連のルーティングルールを共有します。
2.ルーティングルールを作成します
share / routers.js
import Home from './pages/Home'
import List from './pages/List'
export default [
{
path: '/',
component: Home,
exact: true
}, {
path: '/list',
component: List,
}
]
3.サーバー側ルーティングを実装します
- エクスプレスルーティングはすべてのリクエストを受け入れます
エクスプレスルーティングはすべてのリクエストを受け入れます
ExpressルーティングはすべてのGetリクエストを受け入れ、サーバー側のReactルーティングはリクエストパスを介してレンダリングされるコンポーネントと一致します
- サーバー側のルーティング構成
import React from 'react'
import {renderToString} from 'react-dom/server'
import { StaticRouter } from "react-router-dom";
import routes from '../share/routes'
import { renderRoutes } from "react-router-config";
export default (req) => {
const content = renderToString(
<StaticRouter location={req.path}>
{renderRoutes(routes)}
</StaticRouter>
)
return `
<html>
<head>
<title> React SSR</title>
</head>
<body>
<div id="root">${content}</div>
<script src="bundle.js"></script>
</body>
</html>
`
}
4.クライアントルーティングを実装します
クライアントルーティング構成を追加する
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from "react-router-dom"
import { renderRoutes } from "react-router-config";
import routes from '../share/routes'
ReactDOM.hydrate(
<BrowserRouter>
{renderRoutes(routes)}
</BrowserRouter>
, document.getElementById('root'))
5、Reduxのサポート
1.実現分析
React SSRを実装するプロジェクトでは、両端にReduxを実装する必要があります。
クライアントReduxは、クライアントJavaScriptを介してストア内のデータを管理します。
サーバーReduxは、サーバー上に一連のReduxコードを構築して、コンポーネント内のデータ。
クライアントサーバーとサーバーの両方がReducerコードのセットを
共有します。異なるパラメーターの受け渡しのため、ストアを作成するためのコードを共有できません。
ブラウザはデフォルトで非同期関数をサポートしていないため、非同期ディスパッチの作成時にエラーが報告されます
Uncaught ReferenceError:regeneratorRuntimeは
、eval(user.action.js:17)
、fetchUser(user.action.js:44)
、eval(List.js:16)
、invokePassiveEffectCreate(react-dom.development.js:23482 )で定義されていません。 )
HTMLUnknownElement.callCallback(react-dom.development.js:3945)
at Object.invokeGuardedCallbackDev(react-dom.development.js:3994)
at invokeGuardedCallback(react-dom.development.js:4056)
at flushPassiveEffectsImpl(react-dom .development.js:23569)
at不安定な_runWithPriority (scheduler.development.js:646)
at runWithPriority $ 1(react-dom.development.js:11276)
Babelがポリフィルサポートを開始:
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage'
}
],
'@babel/preset-react'
]
}
}
}
2.サーバー側のReduxを実現する
- ストアを作成する
server / createStore.js
import {
createStore, applyMiddleware } from "redux";
import thunk from 'redux-thunk'
import reducer from '../share/store/reducers'
export default () => createStore(reducer, {
}, applyMiddleware(thunk))
- ストアを構成する
server / index.js
import app from './http'
import renderer from './renderer'
import createStore from './createStore'
app.get('*', (req, res) => {
const store = createStore()
res.send(renderer(req, store))
})
server / renderer.js
import React from 'react'
import {
renderToString} from 'react-dom/server'
import {
StaticRouter } from "react-router-dom";
import routes from '../share/routes'
import {
renderRoutes } from "react-router-config";
import {
Provider } from "react-redux";
export default (req, store) => {
const content = renderToString(
<Provider store={
store}>
<StaticRouter location={
req.path}>
{
renderRoutes(routes)}
</StaticRouter>
</Provider>
)
return `
<html>
<head>
<title> React SSR</title>
</head>
<body>
<div id="root">${
content}</div>
<script src="bundle.js"></script>
</body>
</html>
`
}
3.サーバー側のストアデータ入力
問題:サーバー側で作成されたストアが空であり、コンポーネントがストアからデータを取得できません。
解決策:サーバーは、コンポーネントをレンダリングする前に、コンポーネントに必要なデータを取得します。
- loadDataメソッドをコンポーネントに追加します。このメソッドは、コンポーネントに必要なデータを取得するために使用されます。このメソッドはサーバーによって呼び出されます。
- loadDataメソッドを現在のコンポーネントのルーティング情報オブジェクトに保存します。
- サーバーはリクエストを受信すると、リクエストアドレスに従ってレンダリングされるコンポーネントのルーティング情報と一致します
- ルーティング情報からコンポーネントのloadDataメソッドを取得し、メソッドを呼び出してコンポーネントに必要なデータを取得します
- データ収集が完了したら、コンポーネントをレンダリングし、結果をクライアントに応答します
4.警告の除去に反応する
react-dom.development.js:67警告:サーバーのHTMLに
- に
- 。
at ul
at div
at List(webpack://react-ssr/./src/share/pages/List.js?:19:19)
at Connect(List)(webpack://react-ssr/./node_modules/ react-redux / es / components / connectAdvanced.js?:231:68)
at Route(webpack://react-ssr/./node_modules/react-router/esm/react-router.js?:464:29)
at
ルーター(webpack://react-ssr/./node_modules/react-router/ )でスイッチ(webpack://react-ssr/./node_modules/react-router/esm/react-router.js?:670:29)esm / react-router.js?:93:30)
at BrowserRouter(webpack://react-ssr/./node_modules/react-router-dom/esm/react-router-dom.js?:59:35)
atプロバイダー(webpack://react-ssr/./node_modules/react-redux/es/components/Provider.js?:16:20)
警告の理由:クライアントストアには初期状態のデータがありません。コンポーネントをレンダリングすると、空のulが生成されますが、サーバー側が最初にデータを取得してからコンポーネントのレンダリングを実行する
ため、生成されたulは子要素でハイドレイトされます。メソッドを比較したところ、2つに一貫性がないことが判明したため、警告が報告されました。
解決策:サーバーが取得したデータをクライアントに埋め戻し、クライアントが初期データを取得できるようにします。
- サーバーはストアの初期状態に応答します
server / renderer.js
import React from 'react'
import {
renderToString} from 'react-dom/server'
import {
StaticRouter } from "react-router-dom";
import routes from '../share/routes'
import {
renderRoutes } from "react-router-config";
import {
Provider } from "react-redux";
import serialize from 'serialize-javascript'
export default (req, store) => {
const content = renderToString(
<Provider store={
store}>
<StaticRouter location={
req.path}>
{
renderRoutes(routes)}
</StaticRouter>
</Provider>
)
const initialState = JSON.stringify(JSON.parse(serialize(store.getState())))
return `
<html>
<head>
<title> React SSR</title>
</head>
<body>
<div id="root">${
content}</div>
<script>window.INITIAL_STATE = ${
initialState} </script>
<script src="bundle.js"></script>
</body>
</html>
`
}
- クライアントはストアの初期状態を設定します
client / createStore.js
import {
createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import reducer from '../share/store/reducers'
const store = createStore(reducer, window.INITIAL_STATE, applyMiddleware(thunk))
export default store
4.XSS攻撃を防ぐ
遷移状態の悪意のあるコード
let response = {
data: [{
id: 1, name: '<script>alert(1)</script>'}]
}
import serialize from 'serialize-javascript'
const initialState = serialize(store.getState())