linwu-hi / code-interview

前端面试小册,包含Vue面试题,React面试题,JS面试题,HTTP面试题,工程化面试题,CSS面试题,算法面试题,大厂面试题,高频面试题
187 stars 22 forks source link

面试官:说说React服务端渲染怎么做?原理是什么? #127

Open linwu-hi opened 1 year ago

linwu-hi commented 1 year ago

服务端渲染(SSR)

服务端渲染简介

服务端渲染(SSR)是一种用于处理页面的技术,它在服务器端构建HTML结构并将完整的交互式页面发送到浏览器,然后将状态和事件绑定到该页面。SSR主要解决了两个关键问题:

服务端渲染的问题

SEO问题

搜索引擎爬虫可以直接查看完全渲染的页面,而客户端渲染(Client-Side Rendering,简称CSR)可能会导致搜索引擎无法获取页面内容。

首屏加载问题

CSR可能导致首屏白屏,用户体验较差。SSR可以在服务器端渲染首屏,提高加载速度。

SSR示意图

实现服务端渲染(SSR)

创建Express服务器

我们通过Express创建一个服务器,用于监听3000端口的请求,并在根目录请求时返回HTML页面。

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send(`
    <html>
      <head>
        <title>SSR Demo</title>
      </head>
      <body>
        Hello world
      </body>
    </html>
  `);
});

app.listen(3000, () => console.log('Example app listening on port 3000!'));

我们将在服务器端编写React代码,并在app.js中引用它。

使用Webpack进行打包

为了使服务器能够识别JSX,我们需要使用Webpack对项目进行打包转换。创建一个名为webpack.server.js的配置文件,并进行相关配置。

const path = require('path');
const nodeExternals = require('webpack-node-externals');

module.exports = {
  target: 'node',
  mode: 'development',
  entry: './app.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'build'),
  },
  externals: [nodeExternals()],
  module: {
    rules: [
      {
        test: /\.js?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
          presets: ['react', 'stage-0', ['env', { targets: { browsers: ['last 2 versions'] } }]],
        },
      },
    ],
  },
};

使用renderToString渲染React组件

我们借助react-dom提供的renderToString方法,将React组件渲染为HTML字符串。

import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import Home from './src/containers/Home';

const app = express();
const content = renderToString(<Home />);

app.get('/', (req, res) => {
  res.send(`
    <html>
      <head>
        <title>SSR Demo</title>
      </head>
      <body>
        ${content}
      </body>
    </html>
  `);
});

app.listen(3001, () => console.log('Example app listening on port 3001!'));

实现服务端和客户端共用一套代码

在这里插入图片描述

同构概念

同构是指一套React代码在服务器端和客户端都能运行。在同构中,服务端渲染完成页面结构,而浏览器端渲染完成事件绑定和一些交互。这种方式既可以提高首屏加载速度,又能保持页面的交互性。

处理路由问题

在React应用中,通常会存在多个页面和路由的情况。为了在服务端渲染中处理路由,需要配置路由信息。

import React from 'react';
import { Route } from 'react-router-dom';
import Home from './containers/Home';

export default (
  <div>
    <Route path="/" exact component={Home}></Route>
  </div>
);

然后,可以通过index.js引用路由信息:

import React from 'react';
import ReactDom from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import Router from '../Routers';

const App = () => {
  return (
    <BrowserRouter>
      {Router}
    </BrowserRouter>
  );
};

ReactDom.hydrate(<App />, document.getElementById('root'));

解决路由渲染问题

在服务端渲染时,每个Route组件外面包裹着一层div,但在服务端返回的代码中并没有这个div。这可能会导致浏览器端渲染时出现问题。为了解决这个问题,我们需要在服务端执行一遍路由信息,使用StaticRouter来替代BrowserRouter,并通过context参数进行参数传递。

import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import Router from '../Routers';

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

app.get('/', (req, res) => {
  const content = renderToString(
    <StaticRouter location={req.path} context={{}}>
      {Router}
    </StaticRouter>
  );
  res.send(`
    <html>
      <head>
        <title>SSR Demo</title>
      </head>
      <body>
        <div id="root">${content}</div>
        <script src="/index.js"></script>
      </body>
    </html>
  `);
});

app.listen(3001, () => console.log('Example app listening on port 3001!'));

服务端渲染的工作流程

参考文献