几种页面渲染
- 「后端渲染」指传统的 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)