React 组件可以客户端进行渲染。在客户端渲染组件时,是在页面加载完成后才开始渲染的。这会对页面加载效率造成影响,也不利于搜索引擎的抓取(不利于SEO)。基于React 虚拟组件机制,我们可以在服务端对组件进行渲染。在服务端渲当React 组件不仅可以提升页面的加载效率,而且可以服务端在客户端共用组件,提高组件的复用率。本文将通过一个简单的示例,介绍在Node.js Express框架
服务端渲染的实现。
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
属性。
组件渲染后,使用Express
的res.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-browserify
、watchify
模块:
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.js
React 控制文件,并将编译后的脚本输出到public
目录。public
目录默认被设置为Express
的静态目录。
编译
Gulpfile.js
保存到项目根目录后,在命令行中执行以下命令:
gulp
编译完成后,将会生成public/main.js
文件,它将最终在客户端引用。这个文件包含了通过gulp
和browserify
打包的React
、MyComponent
组件等。
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
检测通过,使用了来自服务端的相同状态值进行属性初始化。
注意:这一过程是在页面在加载完成后,在客户端完成的。