Note source: Hook Education – big front end employment Training camp

Content: Notes, thoughts and experiences in the process of learning

Tip: project actual combat article resources can not be uploaded, only for reference

Reactssr-react server rendering

An overview of the

  • Client-side rendering (CSR) : The server only returns JSON DATA. The client receives the DATA, combines it with HTML, and renders it
  • Server-side rendering (SSR) : The server directly combines the DATA and HTML to be used, and then returns the combined HTML to the client. The client only needs to use it directly

There is a problem with server-side rendering

  • Long waiting time on the first screen and poor user experience
  • Page structure is empty, search engines can’t climb any content, not conducive to SEO

ReactSSR isomorphism – server side render isomorphism

  • Isomorphism refers to code reuse, that is, to achieve maximum code reuse on both the client and server

The project structure is initialized

  • The project structure
    • SRC source code folder
      • Client – Client code directory
      • Server – Directory for server-side code
      • Share – Isomorphic code directory

Copy the package.json and package.lock.json files provided by the course into the tutorial project, and then NPM I installs the dependencies

ReactSSR prototype

Create node server-Express

  1. Determine whether express dependencies are already installed
  2. Create the http.js file in the server directory and create the Node server internally
// Introduce dependencies
import express from 'express';

// Create a Node server instance object
const app = express();
// 
//app.use(express.static('public'));
// Listen on port 3000 and print if successful
app.listen(3000.() = > console.log('app is running on 3000 port'));

/ / the node is deduced
export default app;
Copy the code
  1. Create the index.js file in the server directory as the node server entry file
// Import the server instance
import app from './http';

// Accept the request (request object, response object) from the client with the request address /
app.get('/'.(req, res) = >{});// So far, the Node server has been created
Copy the code

Implement ReactSSR

The overall steps

  1. Introduce the React component that needs to be rendered
  2. Convert the React component to an HTML string using the renderToString method
  3. Responds the resulting HTML string to the client

RenderToString is used to convert the React component to an HTML string, imported through the React -dom/server

The specific implementation

  1. Because this component is common to both client and server, it is isomorphic code, written in the share directory

  2. Create a Pages directory under the shart directory to place the page components

  3. The home.js file is created under Pages as the Home page component

    import React, { Component } from 'react'
    
    // Home page component
    export class Home extends Component {
      render() {
        return <div>Content of the Home</div>}}export default Home
    Copy the code
  4. The Home component and renderToString methods are introduced in the server/index.js file

  5. Use renderToString to transform the Home component in the app.get method, and then use res.send to return the HTML page structure

    // Import the server instance
    import app from './http'
    import { renderToString } from 'react-dom/server'
    import Home from '.. /share/pages/Home'
    
    // Accept the request (request object, response object) from the client with the request address /
    app.get('/'.(req, res) = > {
      // Convert the home page to a string
      const content = renderToString(<Home />)
      // Echo back (return HTML directly, insert the converted string of the first page)
      res.send(`
        <html>
          <head>
            <title>ReactSSR</title>
          </head>
          <body>
          <div id='root'>${content}</div>
          </body>
        </html>
      `)})Copy the code

The server application Webpack packages the configuration

ESModule and JSX syntax are not supported in the Node environment, so write must be configured

  1. Create the webpack.server.js file in the project root directory

    // Introduce path to use the API
    const path = require('path')
    
    // Configure objects
    module.exports = {
      // Production environment: development environment
      mode: 'development'.// Code run environment: node
      target: 'node'.// Import file
      entry: './src/server/index.js'./ / export
      output: {
        // Package paths: use path's API for path concatenation
        path: path.join(__dirname, 'build'),
        // Package the file name
        filename: 'bundle.js',},// Configure packaging rules
      module: {
        rules: [{// js file rules
            test: /\.js$/.// Ignore the node_modulse folder
            exclude: /node_modulse/,
            use: {
              // Use the Babel conversion
              loader: 'babel-loader'./ / configuration Babel
              options: {
                / / the Babel prefabrication
                presets: ['@babel/preset-env'.'@babel/preset-react'],},},},],},}Copy the code
  2. Package. js writes commands to perform packaging

    ."scripts": {
        // Execute the webpack.server.js configuration to pack
        "dev:server-build": "webpack --config webpack.server.js",},...Copy the code
  3. An error should be reported when you run the packaged file NPM run dev:server-build using Node

  4. SRC /server/index.js to enable Node to support JSX syntax

    // Import the server instance
    import app from './http'
    import { renderToString } from 'react-dom/server'
    import Home from '.. /share/pages/Home'
    React supports the JSX syntax
    import React from 'react'
    
    // Accept the request (request object, response object) from the client with the request address /
    app.get('/'.(req, res) = > {
      // Convert the home page to a string
      const content = renderToString(<Home />)
      // Echo back (return HTML directly, insert the converted string of the first page)
      res.send(`
        <html>
          <head>
            <title>ReactSSR</title>
          </head>
          <body>
          <div id='root'>${content}</div>
          </body>
        </html>  
      `)})Copy the code
  5. Package.js adds automation commands

    "scripts": {
      	// Add listeners and repackage files if they change
        "dev:server-build": "webpack --config webpack.server.js --watch".// Listen to the build folder and re-execute node build/bundle.js if it changes
        "dev:server-run": "nodemon --watch build --exec \"node build/bundle.js\""
      },
    Copy the code

Add events to the component

Adding events directly to the component does not work, and a look at the source code shows that the JS code does not exist in the packaged file

Implementation idea: on the client side of the component for secondary rendering, add events for the component elements

  • A secondary rendering of the component on the client side using the Hydrate method (introduced via react-DOM) adds events to the component elements
  • Originally we used the Render method, but Render cannot reuse generated nodes, and Hydrate can reuse already generated DOM nodes to save performance overhead

Objective to realize

  • The client writes a JS file to write the secondary rendering code
  • The React JSX and advanced JS syntax requires webpack configuration to run, targeting the public directory
  • Add script tags to the corresponding HTML code of the client to call the packaged JS file
  • The server side realizes static resource response, and the client side JS package file is used as static resource
// SRC /share/pages/ home.js component adds click events

import React, { Component } from 'react'

// Home page component
// export class Home extends Component {
// render() {
// return 
      
console.log(' click event triggered ')}>Home contents
/ /} // } function Home() { // Add click event to print a paragraph return <div onClick={()= >Console. log(' click event triggered ')}>Home content</div> } export default Home // The teacher used the function component, so as to avoid unnecessary error. Copy the code
// SRC /client/index.js client directory new file to write secondary render

import React from 'react'
import ReactDom from 'react-dom'
import Home from '.. /share/pages/Home'

// Do a secondary render on the client side with Hydrate
// Hydrate parameter 1: target component, parameter 2: find the specified element
ReactDom.hydrate(<Home />.document.getElementById('root'))
Copy the code
// webpack.client.js writes the webpack configuration required by the client. Here you can change the path

// Introduce path to use the API
const path = require('path')

// Configure objects
module.exports = {
  // Production environment: development environment
  mode: 'development'.// Import file
  entry: './src/client/index.js'./ / export
  output: {
    // Package paths: use path's API for path concatenation
    path: path.join(__dirname, 'public'),
    // Package the file name
    filename: 'bundle.js',},// Configure packaging rules
  module: {
    rules: [{// js file rules
        test: /\.js$/.// Ignore the node_modulse folder
        exclude: /node_modulse/,
        use: {
          // Use the Babel conversion
          loader: 'babel-loader'./ / configuration Babel
          options: {
            / / the Babel prefabrication
            presets: ['@babel/preset-env'.'@babel/preset-react'],},},},],},}Copy the code
// package.json adds the client packaging command

"scripts": {
  "dev:server-build": "webpack --config webpack.server.js --watch".// Client package command
  "dev:client-build": "webpack --config webpack.client.js --watch"."dev:server-run": "nodemon --watch build --exec \"node build/bundle.js\""
},
Copy the code
// SRC /server/index.js/SRC /server/index.js/SRC /server/index.js/SRC /server/index.js/SRC /server/index.js

// Import the server instance
import app from './http'
import { renderToString } from 'react-dom/server'
import Home from '.. /share/pages/Home'
React supports the JSX syntax
import React from 'react'

// Accept the request (request object, response object) from the client with the request address /
app.get('/'.(req, res) = > {
  // Convert the home page to a string
  const content = renderToString(<Home />)
  // Echo back (return HTML directly, insert the converted string of the first page)
  res.send(`
    <html>
      <head>
        <title>ReactSSR</title>
      </head>
      <body>
        <div id='root'>${content}</div>
        <script src='bundle.js'></script>
      </body>
    </html>  
  `)})Copy the code
// SRC /server/http.js will package the above file as a static file corresponding to the client

// Introduce dependencies
import express from 'express'

// Create a Node server instance object
const app = express()

Use the public folder as a static resource
app.use(express.static('public'))

// Listen on port 3000 and print if successful
app.listen(3000.() = > console.log('app is running on 3000 port'))

/ / the node is deduced
export default app
Copy the code

Components write events => Write secondary render code => Configure packaging parameters => Configure packaging commands => (perform packaging) => Use the packaged file as a static file => Use the packaged file

Optimize the code

Merge the WebPack configuration

  • The server and client webpack have duplicate configuration items, which are abstracted into a file – webpack.base.js
  • Use webpack-merge for merge configuration operations
// webpack.base.js extracts the repeated configuration items from a separate file

// Export duplicate configuration items
module.exports = {
  // Production environment: development environment
  mode: 'development'.// Configure packaging rules
  module: {
    rules: [{// js file rules
        test: /\.js$/.// Ignore the node_modulse folder
        exclude: /node_modulse/,
        use: {
          // Use the Babel conversion
          loader: 'babel-loader'./ / configuration Babel
          options: {
            / / the Babel prefabrication
            presets: ['@babel/preset-env'.'@babel/preset-react'],},},},],},}Copy the code
// webpack.client.js uses the extracted configuration and then writes its own private configuration merge export

// Introduce path to use the API
const path = require('path')
// Introduce duplicate configuration
const baseConfig = require('./webpack.base')
// Introduce the merge configuration method
const merge = require('webpack-merge')

// Configure the object and store it in an object
const config = {
  // Import file
  entry: './src/client/index.js'./ / export
  output: {
    // Package paths: use path's API for path concatenation
    path: path.join(__dirname, 'public'),
    // Package the file name
    filename: 'bundle.js',}}// Merge to export the new configuration
module.exports = merge(baseConfig, config)
Copy the code
// webpack.server.js uses the extracted configuration and then writes its own private configuration merge export

// Introduce path to use the API
const path = require('path')
// Introduce duplicate configuration
const baseConfig = require('./webpack.base')
// Introduce the merge configuration method
const merge = require('webpack-merge')

// Configure the object and store it in an object
const config = {
  // Code run environment: node
  target: 'node'.// Import file
  entry: './src/server/index.js'./ / export
  output: {
    // Package paths: use path's API for path concatenation
    path: path.join(__dirname, 'build'),
    // Package the file name
    filename: 'bundle.js',}}// Merge to export the new configuration
module.exports = merge(baseConfig, config)
Copy the code

Merge the project startup command

To start a project, you need to start three command line tools, all of which need to enter startup commands. Here, all commands are combined into one command to solve the tedious problem of multiple startup commands

Through tools –npm-run-allImplementation, already installed, directly add a new command to the package.json script

"scripts": {
  // dev Run the NPM -run-all command
  The parallel parameter indicates that multiple commands are executed simultaneously
  // dev:* indicates all the commands starting with dev:
  "dev": "npm-run-all --parallel dev:*"."dev:server-build": "webpack --config webpack.server.js --watch"."dev:client-build": "webpack --config webpack.client.js --watch"."dev:server-run": "nodemon --watch build --exec \"node build/bundle.js\""
},
Copy the code

Server side package file volume optimization

  • Q: The node system module is included in the file package, resulting in a large file volume
  • A: In Webpack, remove the Node module from the package file, because the server code runs in the Node environment, there is no need to pack another node module
// Add the culling method to the configuration

// Introduce path to use the API
const path = require('path')
// Introduce duplicate configuration
const baseConfig = require('./webpack.base')
// Introduce the merge configuration method
const merge = require('webpack-merge')
// Introduce the culling package Node module method !!!!!!!!!!!!!!!!!!
const nodeExternals = require('webpack-node-externals')

// Configure the object and store it in an object
const config = {
  // Code run environment: node
  target: 'node'.// Import file
  entry: './src/server/index.js'./ / export
  output: {
    // Package paths: use path's API for path concatenation
    path: path.join(__dirname, 'build'),
    // Package the file name
    filename: 'bundle.js',},// Call method execute cull package node module !!!!!!!!!!!!!!!!
  externals: [nodeExternals()],
}

// Merge to export the new configuration
module.exports = merge(baseConfig, config)
Copy the code

Resolution of the code

  • Q: Start server code and render code for modular split
  • A: The render React component code is A separate function, so it is stored in A separate file
// SRC /server/renderer.js separate the rendering code

import { renderToString } from 'react-dom/server'
import Home from '.. /share/pages/Home'
React supports the JSX syntax
import React from 'react'

// Export a function directly
export default() = > {// Convert the home page to a string
  const content = renderToString(<Home />)
  // Return the required HTML structure
  return `
    <html>
      <head>
        <title>ReactSSR</title>
      </head>
      <body>
        <div id='root'>${content}</div>
        <script src='bundle.js'></script>
      </body>
    </html>
  `
}
Copy the code
// Introduce the rendering code and use it

// Import the server instance
import app from './http'
// Introduce rendering methods
import renderer from './renderer'

// Accept the request (request object, response object) from the client with the request address /
app.get('/'.(req, res) = > {
  // Call the render component method directly using the HTML structure it returns
  res.send(renderer())
})
Copy the code

Routing support

Implementation approach

  • In the React SSR project, both ends of the route should be implemented, that is, the client route and the server route
    • Client-side routing: allows users to click links to jump to pages
    • Server – side routing: Users can access a page directly from the browser address bar
  • Two routes should share the same routine by rule – isomorphic code

Server-side routing implementation

  • Create two new components in the Share/ Pages directory
  • Create a file in the Share directory to write routing rules – object format, because it is isomorphic code in Share
  • Modify the server to receive additional routes and pass down REQ (access information)
  • The rendering code uses route-StaticrOuter, because nodes cannot directly use objects as renderers, and renderRoutes are used to translate the route configuration into components
  • Note: You need to set the browser to disable Javascript during the test, otherwise an error will occur
// SRC /share/pages/List Create a component

import React from 'react'

function List() {
  return <div>List the content</div>
}

export default List
Copy the code
// SRC /share/router.js Writes routing rules in share because they are public routing rules

// Two components
import Home from './pages/Home'
import List from './pages/List'

// Routing rules
export default[{path: '/'.component: Home,
    exact: true}, {path: '/list'.component: List,
  },
]
Copy the code
// SRC /server/index.js modifies the accepted route and passes parameters

// Import the server instance
import app from './http'
// Introduce rendering methods
import renderer from './renderer'

// Accept requests from clients (request object, response object)
// Change can accept all requests
app.get(The '*'.(req, res) = > {
  // Call the render component method directly and use the HTML structure it returns to pass the REq, which contains the routing information
  res.send(renderer(req))
})
Copy the code
// SRC /server/renderer.js replaces the original HOME with a route matching component

import { renderToString } from 'react-dom/server'
React supports the JSX syntax
import React from 'react'
// Implement routing
import { StaticRouter } from 'react-router-dom'
// Route configuration information
import router from '.. /share/router'
// Convert the routing configuration object into a component
import { renderRoutes } from 'react-router-config'

// Export a function directly
export default req => {
  // Convert the home page to a string
  const content = renderToString(
    // Replace the original Home component, StaticRouter matches the component according to req.path
    // renderRoutes(Router) converts required components from object form to components
    <StaticRouter location={req.path}>{renderRoutes(router)}</StaticRouter>
  )
  // Return the required HTML structure
  return `
    <html>
      <head>
        <title>ReactSSR</title>
      </head>
      <body>
        <div id='root'>${content}</div>
        <script src='bundle.js'></script>
      </body>
    </html>
  `
}
Copy the code

Client routing implementation

  • Modify the client index file directly to replace the original Home component with a route matching component
  • Add a link in the Home component to jump to the List
// src/client/index.js

import React from 'react'
import ReactDom from 'react-dom'
import Home from '.. /share/pages/Home'
// Client route matching method
import { BrowserRouter } from 'react-router-dom'
// Convert the routing object to a component
import { renderRoutes } from 'react-router-config'
// Routing rules
import router from '.. /share/router'

// Do a secondary render on the client side with Hydrate
// Hydrate parameter 1: target component, parameter 2: find the specified element
ReactDom.hydrate(
  // BrowserRouter is used to match rules. Directly wrap routing components that need to be used
  RenderRoutes (Router) converts a component from an object to a component
  <BrowserRouter>{renderRoutes(router)}</BrowserRouter>.document.getElementById('root'))Copy the code
// src/share/pages/Home.js

import React, { Component } from 'react'
import { Link } from 'react-router-dom'

// Home page component
// export class Home extends Component {
// render() {
// return 
      
console.log(' click event triggered ')}>Home contents
/ /} // } function Home() { // Add click event to print a paragraph return ( <div onClick={()= >Console. log(' click event trigger ')}> Home contents {/* Add a jump link */}<Link to='/list'>Jump list</Link> </div>)}export default Home Copy the code

Redux support

Implementation approach

  • The React SSR project needs to implement both Redux

    • Client Redux: Studied before
    • Server-side Redux: The server builds a set of Redux code to manage data in components
  • The client and server can share a Reducer code

  • However, the create Store code is not shared due to different parameter passing and needs to be created separately at both ends

Client Redux implementation

  • The client creation file writes the Store creation code
  • Pass the store in the client index file, using the Thunk middleware
  • Reducer is a reconstruction code, so it is written in share (Reducer, action)
  • Create the Reducr and Action directories respectively and write the response code
  • The reducer finally needs to merge and export
  • An error may be reported because the browser does not support asynchronous functions by default and you may need to set up a Bable prefab
// SRC /client/ cratestore.js creates a new file to create a store

import { createStore, applyMiddleware } from 'redux'
// Middleware uses thunk
import thunk from 'redux-thunk'
// Import the merged reducer
import Reducer from '.. /share/store/reducers'

/ / create a store
const store = createStore(Reducer, {}, applyMiddleware(thunk))

export default store
Copy the code
// SRC /client/index.js creates the store and passes it on

import React from 'react'
import ReactDom from 'react-dom'
// Client route matching method
import { BrowserRouter } from 'react-router-dom'
// Convert the routing object to a component
import { renderRoutes } from 'react-router-config'
// Routing rules
import router from '.. /share/router'
import { Provider } from 'react-redux'
import store from './crateStore'

// Do a secondary render on the client side with Hydrate
// Hydrate parameter 1: target component, parameter 2: find the specified element
ReactDom.hydrate(
  / / store
  <Provider store={store}>{/* BrowserRouter is used to match rules. RenderRoutes (Router) converts a component from an object to a component.<BrowserRouter>{renderRoutes(router)}</BrowserRouter>
  </Provider>.document.getElementById('root'))Copy the code
/ / SRC/share/store/actions/user. The action. The js file to create new action

import axios from 'axios'

// Save the user action
export const saveUser = 'save_user'

// Get the user directive
export const getUser = () = > async dispatch => {
  // Request data
  const { data } = await axios.get('https://jsonplaceholder.typicode.com/users')
  Call another action
  dispatch({ type: saveUser, payload: data })
}
Copy the code
/ / SRC/share/store/reducers/user. The reducer, js file to create new reducer

import { saveUser } from '.. /actions/user.action'

// reducer
const userReducer = (state = [], action) = > {
  switch (action.type) {
    case saveUser:
      return action.payload
    default:
      return state
  }
}

export default userReducer
Copy the code
/ / SRC/share/store/reducers/index. The js file merging new reducer

import { combineReducers } from 'redux'
import userReducer from './user.reducer'

/ / merge reducer
export default combineReducers({
  user: userReducer,
})
Copy the code
The // SRC /share/pages/ list.js component hangs behind to get the data and use it

import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import { getUser } from '.. /store/actions/user.action'

function List({ user, dispatch }) {
  // Get data after mount
  useEffect(() = > {
    dispatch(getUser())
  }, [])
  return (
    <div>List the content<ul>{/* Traverses the user creation structure */} {user.map(item => (<li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>)}// Get store data
const data = state= > ({
  user: state.user,
})

export default connect(data)(List)
Copy the code
// webpack.base.js changes the public Webpack configuration to support asynchronous functions (not supported by browsers by default)

presets: [
  // Change the first parameter to support asynchronous functions
  [
    '@babel/preset-env',
    {
      useBuiltIns: 'usage'],},'@babel/preset-react',].Copy the code

Server-side Redux implementation

  • Store create function: create a file to create a store create function, which is created later when the request is received

  • Configure the store:

    • The server creates a store when it receives the request and passes the store to the render logic code
    • Pass the store in the render logic
  • Server-side store data population: The currently created store is empty and the component cannot fetch data from the store. The component needs to fetch the data it needs to use before rendering the component on the server

    • A loadData method is added to the component to get the data the component needs to use. The method is called on the server side
    • Store the loadData method in the current component routing information object
    • The server side accepts the request and matches the routing information to render based on the address
    • Get the loadData method from the routing information and call get data
    • After the data is retrieved, the component renders the response to the client
  • Eliminate warnings: Client store there is no data in the initial state, at the time of rendering component to generate an empty ul, but the server is first to get the data for rendering, so generated is a child element of ul, hydrate when doing the comparison found inconsistencies, so the warning, just need to access to the data on the server backfill to the client, Let the client own the initial data as well

    • Get store state data before rendering on the server
    • Then save this data to the Window object, noting the string conversion
    • The client passes the window property to the second parameter when creating a store
/ / SRC/server/createStore js file to create new store on the server to create function

import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
Reducer shared with the client
import reducers from '.. /share/store/reducers'

// Export a function that returns store
export default () => createStore(reducers, {}, applyMiddleware(thunk))
Copy the code
// SRC /server/index.js server creates store and passes it to render to prepare for rendering

// Import the server instance
// Matches the routing rule information
import { matchRoutes } from 'react-router-config'
// Routing rules
import router from '.. /share/router'
// store creates the function
import createStore from './createStore'
import app from './http'
// Introduce rendering methods
import renderer from './renderer'

// Accept the request (request object, response object) from the client with the request address /
// Modify can receive multiple routes
app.get(The '*'.(req, res) = > {
  / / create a store
  const store = createStore()
  Parameter 1: routing rule, parameter 2: matching rule, matching all information about the current route - array
  // Displays all matching success messages
  // Finally returns a Promise array
  const promises = matchRoutes(router, req.path).map(({ route }) = > {
    // If loadData exists, execute with the store parameter
    if (route.loadData) return route.loadData(store)
  })
  // Iterate through the Promise array. If it can execute to THEN, it is ok. Continue rendering
  Promise.all(promises).then(() = > {
    // Call the render component method directly and use the HTML structure it returns to pass the REq, which contains the routing information
    res.send(renderer(req, store))
  })
})
Copy the code
// SRC /server/renderer.js render logic passes down the received store and stores the initial store

import { renderToString } from 'react-dom/server'
React supports the JSX syntax
import React from 'react'
// Implement routing
import { StaticRouter } from 'react-router-dom'
// Route configuration information
import router from '.. /share/router'
// Convert the routing configuration object into a component
import { renderRoutes } from 'react-router-config'
import { Provider } from 'react-redux'

// Export a function directly
export default (req, store) => {
  // Get the current store content
  const myState = store.getState()
  // Convert the home page to a string
  const content = renderToString(
    // Replace the original Home component, StaticRouter matches the component according to req.path
    // renderRoutes(Router) converts required components from object form to components
    // Provider passes store
    <Provider store={store}>
      <StaticRouter location={req.path}>{renderRoutes(router)}</StaticRouter>
    </Provider>
  )
  // Return the required HTML structure, store the obtained store contents in the window, for the client to use, avoid alarm
  return `
    <html>
      <head>
        <title>ReactSSR</title>
      </head>
      <body>
        <div id='root'>${content}</div>
        <script>window.myState=The ${JSON.stringify(myState)}</script>
        <script src='bundle.js'></script>
      </body>
    </html>
  `
}
Copy the code
// SRC /share/pages/ list.js component creation method for server to call to initialize store

import React, { useEffect } from 'react'
import { connect } from 'react-redux'
// action
import { getUser } from '.. /store/actions/user.action'

function List({ user, dispatch }) {
  // Get data after mount
  useEffect(() = > {
    dispatch(getUser())
  }, [])
  return (
    <div>List the content<ul>{/* Traverses the user creation structure */} {user.map(item => (<li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>)}// Get store data
const data = state= > ({
  user: state.user,
})

// Create a function for the server to call. The server can call this method directly to store operations
// parameter -store
function loadData(store) {
  // Call action directly to get the data
  return store.dispatch(getUser())
}

// Return an array to make the structure clearer
export default { component: connect(data)(List), loadData }
Copy the code
// SRC /share/router.js Routing rules Modify the list component rules

// Two components
import Home from './pages/Home'
import List from './pages/List'

// Routing rules
export default[{path: '/'.component: Home,
    exact: true}, {path: '/list'.// Expand List directly. List, }, ]Copy the code
// SRC /client/ cratestore. js Server store Is created using the store stored on the server

import { createStore, applyMiddleware } from 'redux'
// Middleware uses thunk
import thunk from 'redux-thunk'
// Import the merged reducer
import Reducer from '.. /share/store/reducers'

// Create store, the second parameter uses the server stored in the window injury data, avoid alarm
const store = createStore(Reducer, window.myState, applyMiddleware(thunk))

export default store
Copy the code

Prevent XSS attacks

  • When the server returns data with malicious JS code, the code may be rendered by the browser
  • This can be avoided by serialize-javascript processing of the data
  • Store data obtained on the server side is processed, and then the processed results are used
// SRC /server/renderer.js handles data when storing stores to avoid XSS attacks

import { renderToString } from 'react-dom/server'
React supports the JSX syntax
import React from 'react'
// Implement routing
import { StaticRouter } from 'react-router-dom'
// Route configuration information
import router from '.. /share/router'
// Convert the routing configuration object into a component
import { renderRoutes } from 'react-router-config'
import { Provider } from 'react-redux'
import serialize from 'serialize-javascript'

// Export a function directly
export default (req, store) => {
  // Get the current store contents and use serialize to process the store to avoid XSS attacks
  const myState = serialize(store.getState()) // const myState = JSON.stringify(store.getState())
  // Convert the home page to a string
  const content = renderToString(
    // Replace the original Home component, StaticRouter matches the component according to req.path
    // renderRoutes(Router) converts required components from object form to components
    // Provider passes store
    <Provider store={store}>
      <StaticRouter location={req.path}>{renderRoutes(router)}</StaticRouter>
    </Provider>
  )
  // Return the required HTML structure, store the obtained store contents in the window, for the client to use, avoid alarm
  return `
    <html>
      <head>
        <title>ReactSSR</title>
      </head>
      <body>
        <div id='root'>${content}</div>
        <script>window.myState=${myState}</script>
        <script src='bundle.js'></script>
      </body>
    </html>
  `
}
Copy the code