服务端渲染

574 阅读2分钟

几种页面渲染

  • 「后端渲染」指传统的 ASP、Java 或 PHP 的渲染机制
  • 「前端渲染」指使用 JS 来渲染页面大部分内容,代表是现在流行的 SPA 单页面应用
    • 局部刷新
    • 懒加载
    • 富交互
    • 节约服务器成本
    • 天生的关注分离设计
  • 「SPA」就是只有一张Web页面的应用,在Web端使用了history或hash API,通过路由作为中心枢纽控制一系列页面(组件)的渲染加载和数据交互
  • 「同构渲染」指前后端共用 JS,首次渲染时使用 Node.js 来直出HTML
    • 优点:
      • SEO优化
      • 首屏加载
      • 使用同一套js代码

同构服务端渲染

1. 概念

  • ReactDOMServer.renderToString(element)将 React 元素渲染为初始 HTML
  • ReactDOM.hydrate(element, container[, callback])但它用于在 ReactDOMServer 渲染的容器中对 HTML 的内容进行 hydrate 操作。React 会尝试在已有标记上绑定事件监听器

2. 步骤

  • server 中使用 Koa 路由监听 页面访问
    import * as Router from 'koa-router'
    const router = new Router()
    
    // 如果中间也提供 Api 层
    router.use('/api/home', async () => {
        // 返回数据
    })
    router.get('*', async (ctx) => {
        // 返回 HTML
    })
    
  • 通过访问 url 匹配 前端页面路由
    // 前端页面路由
    import { pages } from '../../client/app'
    import { matchPath } from 'react-router-dom'
    
    // 使用 react-router 库提供的一个匹配方法
    const matchPage = matchPath(ctx.req.url, page)
    
  • 通过页面路由的配置进行 数据获取。通常可以在页面路由中增加 SSR 相关的静态配置,用于抽象逻辑,可以保证服务端逻辑的通用性,如:
    class HomePage extends React.Component{
        public static ssrConfig = {
            cache: true,
            fetch() {
                // 请求获取数据
            }
        }
    }
    
  • 创建 Redux store,并将数据dispatch到里面
    import { createStore } from 'redux'
    // 获取 Clinet层 reducer
    // 必须复用前端层的逻辑,才能保证一致性;
    import { reducers } from '../../client/store'
    
    // 创建 store
    const store = createStore(reducers)
    // 获取配置好的 Action
    const action = ssrConfig.action
    // 存储数据	
    store.dispatch(createAction(action)(data))
    
  • 注入 Store, 调用renderToString将 React Virtual Dom 渲染成 字符串
    import * as ReactDOMServer from 'react-dom/server'
    import { Provider } from 'react-redux'
    
    // 获取 Clinet 层根组件
    import { App } from '../../client/app'
    const AppString = ReactDOMServer.renderToString(
    	<Provider store={store}>
    		<StaticRouter
    			location={ctx.req.url}
    			context={{}}>
    			<App />
    		</StaticRouter>
    	</Provider>
    )
    
  • 将 AppString 包装成完整的 html 文件格式
  • 此时,已经能生成完整的 HTML 文件。但只是个纯静态的页面,没有样式没有交互。接下来我们就是要插入 JS 与 CSS
    const html = `
        <!DOCTYPE html>
        <html lang="zh">
            <head></head>
            <link href="${cssPath}" rel="stylesheet" />
            <body>
                <div id="App">${AppString}</div>
                <script src="${scriptPath}"></script>
            </body>
        </html>
    `
    
  • Server 数据脱水: 把服务端获取的数据同步到前端。将数据序列化后,插入到 html 中,返回给前端
    import serialize from 'serialize-javascript'
      
    // 获取数据
    const initState = store.getState()
    const html = `
        <!DOCTYPE html>
        <html lang="zh">
            <head></head>
            <body>
                <div id="App"></div>
                <script type="application/json" id="SSR_HYDRATED_DATA">${serialize(initState)}</script>
            </body>
        </html>
    `
    ctx.status = 200
    ctx.body = html
    
  • Client 数据吸水: 初始化 store 时,以脱水后的数据为初始化数据,同步创建 store
    const hydratedEl = document.getElementById('SSR_HYDRATED_DATA')
    const hydrateData = JSON.parse(hydratedEl.textContent)
    // 使用初始 state 创建 Redux store
    const store = createStore(reducer, hydrateData)