服务端渲染 Node 层直接获取数据

在 Egg 项目如果使用模板引擎规范时是通过 render 方法进行模板渲染,render 的第一个参数模板路径,第二个参数时模板渲染数据. 如如下调用方式:

  1. async index(ctx) {
  2. // 获取数据,可以是从数据库,后端 Http 接口 等形式
  3. const list = ctx.service.article.getArtilceList();
  4. // 对模板进行渲染,这里的 index.js 是 React jsx 文件通过 Webpack 构建的 JSBundle 文件
  5. await ctx.render('index.js', { list });
  6. }

从上面的例子可以看出,这种使用方式是非常典型的也容易理解的模板渲染方式。在实际业务开发时,对于常规的页面渲染也建议使用这种方式获取数据,然后进行页面渲染。Node 获取数据后,在 React 文件里面就可以通过 this.props.list 的方式拿到 Node 获取的数据,然后就可以进行 React 模板 jsx 文件数据绑定了。

在这里有个高阶用法,可以直接把 ctx 等 Node 对象传递到第二个参数里面, 这个时候你在模板里面就直接拿到 ctx 这些对象。 但这个时候就需要自己处理好 SSR 渲染时导致的 hydrate 问题,因为前端hydrate时并没有 ctx 对象。

  1. async index(ctx) {
  2. // 获取数据,可以是从数据库,后端 Http 接口 等形式
  3. const list = ctx.service.article.getArtilceList();
  4. // 对模板进行渲染,这里的 index.js 是 React jsx 文件通过 Webpack 构建的 JSBundle 文件
  5. await ctx.render('index.js', { ctx, list });
  6. }

如果把 ctx 对象传递到 React JSX 模板文件里面,你可以通过在 React jsx 模板文件进行 ctx.service.article.getArtilceList() 调用, 也就是在前端代码模板里面进行 Node 调用。

服务端渲染 asyncData 方式获取数据

在 React 进行 SSR 时涉及数据的请求方式,除了 Node 层直接获取数据方式可以继续使用,还可以在 React 组件里面编写 asyncData 方式获取数据。具体实现见 egg-view-react-ssr (^3.0.0) 插件 asyncData 逻辑处理。  

egg-view-react-ssr 插件会判断 render 的组件是否包含静态的 asyncData 方法 (可以是 async 形式或者返回 Promise 方法),如果包括,则触发 asyncData 逻辑,然后获取的到数据与 Node 端的数据进行合并。

React 组件代码

${root}/app/web/page/test.jsx

  1. 'use strict';
  2. import React, { Component } from 'react';
  3. import request from '../../framework/request';
  4. class AsyncDataMode extends Component {
  5. static async asyncData(locals) {
  6. return request.get(`/test/api/article`, locals);
  7. }
  8. render() {
  9. const { title, article } = this.props;
  10. return <div>
  11. <h1 className="easy-article-detail-title">{title}</h1>
  12. <h2 className="easy-article-detail-title">{article.title}</h2>
  13. <div className="easy-article-info">
  14. <iframe src={article.url} frameBorder="0" width="100%" style={{minHeight: '800px'}}></iframe>
  15. </div>
  16. </div>;
  17. }
  18. }
  19. export default AsyncDataMode;

Node 渲染逻辑

添加 ${root}/app/controller/test.js Egg 路由配置

  1. 'use strict';
  2. module.exports = app => {
  3. return class TestController extends app.Controller {
  4. async index(ctx) {
  5. const title = 'Frontend asyncData 获取渲染数据';
  6. await ctx.render('test.js', { title });
  7. }
  8. async article(ctx) {
  9. const article = await ctx.service.article.query({ id: 1 });
  10. ctx.body = { article };
  11. }
  12. };
  13. };

Egg 路由配置

添加 ${root}/app/router.js Egg 路由配置

  1. module.exports = app => {
  2. const { router, controller } = app;
  3. router.get('/test', controller.test.index);
  4. router.get('/test/api/article', controller.test.article);
  5. };

Webpack 构建配置

添加 ${root}/webpack.config.js 新增页面 entry 配置

  1. module.exports = {
  2. entry: {
  3. test: 'app/web/page/test.jsx',
  4. }
  5. }