Reactはサーバー側のレンダリングをネイティブに実装します

Reactはサーバー側のレンダリングをネイティブに実装します

記事の出典:Lagouの大きなフロントエンドの高給トレーニングキャンプ

練習コードアドレス

1.サーバー側のレンダリングがすぐに開始されます

1. ReactSSRを実装する

  1. レンダリングするReactコンポーネントを紹介します
  2. renderToStringメソッドを使用してReactコンポーネントをHTML文字列に変換します
  3. 結果のHTML文字列をクライアントに応答します

renderToStringはじめに、HTML文字列を変換するためのReactアセンブリメソッドreact-dom/server

2.Webpackパッケージ構成

問題:ノード環境はESModuleモジュールシステムをサポートしておらず、JSX構文をサポートしていません

3.プロジェクト起動コマンドの構成

  1. サーバー側のパッケージ化コマンドを構成します。 "dev:server-build": "webpack --config webpack.server.js --watch"
  2. サーバー開始コマンドを構成します。 "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パッケージ構成

  1. webpackの構成
    パッケージ化の目的:JSX構文を変換し、ブラウザーで認識されない高度なJavaScript構文を変換します。
    パッケージの宛先:パブリックフォルダー

  2. パッケージ起動コマンドの設定

"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.サーバー側ルーティングを実装します

  1. エクスプレスルーティングはすべてのリクエストを受け入れます
    エクスプレスルーティングはすべてのリクエストを受け入れます

ExpressルーティングはすべてのGetリクエストを受け入れ、サーバー側のReactルーティングはリクエストパスを介してレンダリングされるコンポーネントと一致します

  1. サーバー側のルーティング構成
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を実現する

  1. ストアを作成する

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))
  1. ストアを構成する

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.サーバー側のストアデータ入力

問題:サーバー側で作成されたストアが空であり、コンポーネントがストアからデータを取得できません。
解決策:サーバーは、コンポーネントをレンダリングする前に、コンポーネントに必要なデータを取得します。

  1. loadDataメソッドをコンポーネントに追加します。このメソッドは、コンポーネントに必要なデータを取得するために使用されます。このメソッドはサーバーによって呼び出されます。
  2. loadDataメソッドを現在のコンポーネントのルーティング情報オブジェクトに保存します。
  3. サーバーはリクエストを受信すると、リクエストアドレスに従ってレンダリングされるコンポーネントのルーティング情報と一致します
  4. ルーティング情報からコンポーネントのloadDataメソッドを取得し、メソッドを呼び出してコンポーネントに必要なデータを取得します
  5. データ収集が完了したら、コンポーネントをレンダリングし、結果をクライアントに応答します

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つに一貫性がないことが判明したため、警告が報告されました。
解決策:サーバーが取得したデータをクライアントに埋め戻し、クライアントが初期データを取得できるようにします。

  1. サーバーはストアの初期状態に応答します

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>
  `
}
  1. クライアントはストアの初期状態を設定します

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())

おすすめ

転載: blog.csdn.net/jal517486222/article/details/112798533