关于服务端渲染的一点点理解

157 阅读2分钟

客户端渲染和服务端渲染是什么?

客户端渲染

客户端渲染一般是单页面的方式来加载所有的资源文件,会被打包成js 文件和一些静态资源文件(如图片,css样式,一些字体等等)。一些HTML元素是需要通过JS加载和执行完之后,才会渲染的。比如在React和vue 单页面应用中,一些组件,需要等待整个框架加载完之后,才可以渲染。

服务端渲染

服务端渲染,是指服务端完成一部分的渲染工作。把HTML直接返回给客户端。

服务端渲染解决的问题

1、用于首屏加载速度过慢的问题,可以让用户的等待时间更短,体验感更好。

2、更好的搜索引擎优化。

3、减轻客户端负担,将部分的渲染工作,在服务端完成,可以减少浏览器压力。

4、避免客户端渲染的闪烁问题。

一些前置知识

import { renderToString } from 'react-dom/server'

  • renderToString

renderToString 将 React 树渲染为一个 HTML 字符串。

  • ReactDOM.hydrate

浏览器通过 hydrate 把 dom 关联到 fiber 树,加上交互逻辑和再次渲染。

服务端渲染如何实现的?

1、使用StaticRouter 和renderToStirng 方法将对应的组件解析为HTML,然后加载客户端的client.js,最后通过服务端返回给用户。

如下代码所示,

 let html = renderToString(
    <Provider store={store}>
        <StaticRouter context={context} location={req.path}>
            {renderRoutes(routes)}
        </StaticRouter>
    </Provider>
)

res.send(`<html>
    <head>
    ${helmet.title.toString()}
    ${helmet.meta.toString()}
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" />
    <style>${cssStr}</style>
    </head>
    <body>
    <div id="root">${html}</div>
    <script>
      // 服务端:组件初始化时会请求数据,请求的数据会存到服务端仓库中,然后组件使用数据显示相应内容
      // 客户端:为了避免组件挂载时又一次的请求数据(当服务器端已经请求过数据并返回了有数据的内容)
      // 所以这里要获取下存在服务端仓库中的数据并作为初始值存到 window 中
      // 俗称:数据的脱水
      window.context = {
          state:${JSON.stringify(store.getState())}
      }
    </script>
    <script src="/client.js"></script>
    </body>
</html>`)

2、使用ReactDOM.hydrate 方法,将服务端渲染未完成的工作完成,比如说绑定事件,继续交给客户端来做。

import routes from "../routes";
import { BrowserRouter, Route } from "react-router-dom";
import { Provider } from "react-redux";
import { getClientStore } from "../store";
import { renderRoutes, matchRoutes } from "react-router-config";

// hydrate 就是表示把服务器端渲染未完成的工作完成,比如说绑定事件
ReactDOM.hydrate(
  <Provider store={getClientStore()}>
    <BrowserRouter>{renderRoutes(routes)}</BrowserRouter>
  </Provider>,
  document.getElementById("root")
);
  1. 客户端和服务端分别配置webpack 进行打包。
  • 客户端配置如下:
module.exports = merge(base, {
    entry: './src/client/index.js',
    output: {
        path: path.resolve('public'),
        filename: 'client.js'
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            modules: true
                        }
                    }
                ]
            }
        ]
    }
})
  • 服务端配置如下:
  // 注意这个值
    target: 'node',
    entry: './src/server/index.js',
    output: {
        path: path.resolve('build'),
        filename: 'server.js'
    },
    externals: [nodeExternal()],
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    // 服务端渲染不能用 style-loader,因为 node 没有 document 对象,无法插入 style 标签
                    // 服务端本来就不能渲染 dom,只是提供 html/css/js 代码给浏览器,交给浏览器去渲染
                    // 服务端返回的 html 源码里,没有 style 标签
                    // 而在浏览器中的 html 源码里,有 style 标签,是通过 js 插入进去的
                    'isomorphic-style-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            modules: true
                        }
                    }
                ]
            }
        ]
    }
})