React Node.js(Express)服务端渲染、服务端/客户端组件共享示例

 2016年04月04日    1141     声明


React 组件可以客户端进行渲染。在客户端渲染组件时,是在页面加载完成后才开始渲染的。这会对页面加载效率造成影响,也不利于搜索引擎的抓取(不利于SEO)。基于React 虚拟组件机制,我们可以在服务端对组件进行渲染。在服务端渲当React 组件不仅可以提升页面的加载效率,而且可以服务端在客户端共用组件,提高组件的复用率。本文将通过一个简单的示例,介绍在Node.js Express框架服务端渲染的实现。


  1. 项目准备
  2. React相关模块安装
  3. 创建组件
  4. 组件的服务器端渲染
  5. 客户端/服务器端组件共享

1. 项目准备

在本示例中,我们将创建一个Express项目。请选择合适版本Node.js下载并安装,安装完成后全局安装express模块。

配置完项目环境,执行以下命令创建项目:

express -e server-render

命令执行完成,就创建了server-render项目。这个项目同样也是我们在React 服务端渲染中使用的示例,这个项目可以在GIT中查看详细代码。

在上面命令中,我们使用了-e参数,这个参数指定了使用ejs模版。


创建完成后,进入server-render目录,npm安装express所需的模块:

npm install


2. React相关模块安装

2.1 安装React模块

和客户端React 开发一样,首先需要核心模块react,还需要react-dom模块来支持DOM相关功能。使用以下命令安装这两个模块:

npm install react react-dom --save


2.2 安装JSX语法支持模块

Node.js使用的JavaScript语言,Node.js默认不会解析React JSX语法。可以使用node-jsx模块来添加对JSX语法的支持:

npm install node-jsx --save

引入node-jsx模块

node-jsx模块安装完成后,在app.js引用这个模块,就可以实现对React JSX模块的支持:

// 注意要在引入'路由'支持,引入node-jsx模块
require('node-jsx').install();
var routes = require('./routes/index');


3. 创建组件

3.1 组件说明

接下来,我们将创建一个简单的React 组件,这个组件在实际中可能并没有太大意义。但通过这个组件,我们可以了解到:服务器/客户端组件的复用、组件的服务器/客户端状态共享、组件的服务端渲染等知识。

这个组件输出类似如下:

<h2<随机数</h2>

为了提高组件的复用率,我们会在前后端使用同样的组件。在实现应用中,前后端的共用组件会涉及状态共享的问题,在这个项目中将共享的数据直接渲染到了页面中,以保持react-checksum检测的有效。

在服务端的组件HTML会随页面的HTML一起渲染,并在页面同步加载。

页面加载后,在客户端使用相同的组件对虚拟DOM进行一次更新。


3.2 组件的定义

为了便于管理,创建一个server-render/components目录用于存放组件。

创建MyComponent组件,该组件保存为components/MyComponent.js

/* 一个客户端、服务端通用的React组件 */
var React = require("react");

var MyComponent = React.createClass({
  render: function() {
    return (<h2>{this.props.number}</h2>);
  }
});

module.exports = MyComponent;

MyComponent组件即可以用于服务端,又可用于客户端。

在这个组件中,定义了一个number属性,在渲染组件时,需要对该属性进行初始化。


4. 组件的服务器端渲染

4.1 创建Express视图

Express本身也是MVC结构的Web框架,我们可以React 组件用于Express的视图(View)层。

创建一个Express视图文件:views/random-props.ejs。在服务端渲染这个文件时,也同时将React 组件从服务端渲染到页面中:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>React 服务端渲染</title>
  </head>
  <body>
    <!-- 挂载React组件 -->
    <div id="example"><%- html %></div>
  </body>
</html>

在这个视图文件中,React 组件渲染后生成的HTML,将由服务端传递到<%- html %>位置。


4.2 服务端渲染组件与组件的服务端挂载

在这个示例中,只使用了一个页面,即:站点的根目录。在根目录('/')渲染视图文件及React 组件(routes/index.js):

var React = require('react');
var ReacDOMServer = require('react-dom/server');
// 服务端引入MyComponent组件
var MyComponent =  React.createFactory(require('../components/MyComponent'));

/* 主页 */
router.get('/', function(req, res) {
  var num = Math.random();
  // 服务端渲染HTML
  var html = ReacDOMServer.renderToString(<MyComponent number={num} />);
  // 向页面传递渲染后HTML字符串和num(props)
  res.render('random-props', {html:html, num:num});
});

通过ReacDOMServer.renderToString()方法,将React 组件渲染成了HTML字符串。在渲染时,传入了一个num变量,这个变量用于初始化组件的number属性。

组件渲染后,使用Expressres.render()方法渲染Express 视图。在渲染Express 视图时,通过html变量将MyComponent组件渲染后的HTML传递到了Express 视图中。这样在React 组件会和页面同步加载到浏览器中。

在上例中,除了向Express 视图传递了html变量外,还传递了num变量,在客户端使用相同组件时,使用该变量共享状态。


4.3 效果查看

完成上面一步,我们已经完成了React 组件在Node.js Express框架服务端的渲染。现在可以运行项目,并查看渲染结果:

node bin/www

运行后,在浏览器输入以下网址查看渲染效果:

http://127.0.0.1:3000/
// 或
http://localhost:3000/


5. 客户端/服务器端组件共享

5.1 引用组件

要在客户端使用组件,需要建立一个.js控制文件,用于引用MyComponent组件、客户端渲染组件及将组件挂到到DOM中。

在本示例中,控制文件为components/main.js

/* 客户端通过这个文件操作React组件和DOM */
var React = require('react');
var ReactDOM = require('react-dom');
// 客户端引入MyComponent组件
var MyComponent = require('./MyComponent');

var mountNode = document.getElementById('example');
// 使用服务端传递初始值重新渲染组件
ReactDOM.render(<MyComponent number={initProps.num} /<, mountNode);


5.2 组件的编译

Node.js中使用CommonJS 模块定义规范main.js控制文件及MyComponent也同样使用了CommonJS规范,该规范并不能直接在浏览器环境中运行。所以,需要对其进行编译。

编译模块安装

在本示例中,使用Gulp.JS打包前端使用的脚本,Browserify模块的gulp-browserify中间件解决CommonJS模块在浏览器运行环境的依赖,watchify模块转换React JSX语法。

全局安装gulp模块后,在开发环境安装gulp-browserifywatchify模块:

npm install gulp-browserify watchify --save-dev


编写编译脚本

模块安装完成后,编写编译脚本。编译脚本为Gulpfile.js

var gulp = require('gulp');
var browserify = require('gulp-browserify');

gulp.task('scripts', function () {
  gulp.src(['components/main.js'])
    .pipe(browserify({
      debug: true,
      transform: [ 'reactify' ]
    }))
    .pipe(gulp.dest('./public/'));
});

gulp.task('default', ['scripts']);

在这个脚本中,引入了components/main.jsReact 控制文件,并将编译后的脚本输出到public目录。public目录默认被设置为Express的静态目录。


编译

Gulpfile.js保存到项目根目录后,在命令行中执行以下命令:

gulp

编译完成后,将会生成public/main.js文件,它将最终在客户端引用。这个文件包含了通过gulpbrowserify打包的ReactMyComponent组件等。


5.3 在客户端使用组件

为了在客户端使用MyComponent组件,我们将Express的视图文件views/random-props.ejs修改如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>React 服务端渲染</title>
  </head>
  <body>
    <!-- 挂载React组件 -->
    <div id="example"><%- html %></div>
    <script type="text/javascript">
      // 传递服务端状态
      var initProps = {num:<%- num %>};
    </script>
    <!-- main.js包含通过gulp和browserify 打包的React、MyComponent组件等 -->
    <script src="/main.js"></script>
  </body>
</html>

修改后,通过initProps变量接收了来自服务器端的组件状态(num变量),并引入了前面编译的public/main.js文件,public是静太目录,所以直接通过/main.js引用即可。

客户端会在页面加载完成后,对MyComponent组件重新渲染一次。为了保持react-checksum检测通过,使用了来自服务端的相同状态值进行属性初始化。

注意:这一过程是在页面在加载完成后,在客户端完成的。