Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
linwu-hi opened this issue Jul 30, 2023 · 0 comments
Open
Labels

Comments

@linwu-hi
Copy link
Owner

linwu-hi commented Jul 30, 2023

服务端渲染(SSR)

服务端渲染简介

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

  • 搜索引擎优化(SEO):搜索引擎爬虫可以直接查看完全渲染的页面,因此SSR有助于提高网站的搜索引擎排名。
  • 加速首屏加载: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!'));

服务端渲染的工作流程

    1. 服务器(Node.js)接收客户端请求,并获取当前请求的URL路径。
    1. 服务器根据URL路径在已有的路由表中查找到对应的React组件,然后获取组件需要的数据。
    1. 服务器将数据作为propscontextstore等形式传递给React组件。
    1. 使用React内置的服务端渲染方法renderToString()将React组件渲染为HTML字符串。
    1. 服务器在将最终的HTML响应发送给浏览器之前,将数据注入到HTML中。
    1. 浏览器开始渲染页面,执行节点对比,然后执行组件内事件绑定和交互。

参考文献

@linwu-hi linwu-hi added the react label Jul 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant