[译]React 在服务端渲染的实现
- 原文地址:Server-Side React Rendering
- 原文作者:Roger Jin
React 在服务端渲染的实现
React是最受欢迎的客户端 JavaScript 框架,但你知道吗(可以试试),你可以使用 React 在服务器端进行渲染?
假设你已经在客户端使用 React 构建了一个事件列表 app。该应用程序使用了您最喜欢的服务器端工具构建的API。几周后,用户告诉您,他们的页面没有显示在 Google 上,发布到 Facebook 时也显示不出来。 这些问题似乎是可以解决的,对吧?
您会发现,要解决这个问题,需要在初始加载时从服务器渲染 React 页面,以便来自搜索引擎和社交媒体网站的爬虫工具可以读取您的标记。有证据表明,Google 有时会执行 javascript 程序并且对生成的内容进行索引,但并不总是的。因此,如果您希望确保与其他服务(如Facebook,Twitter)有良好的SEO兼容性,那么始终建议使用服务器端渲染。
在本教程中,我们将逐步介绍服务器端的呈现示例。包括围绕与API交流的React应用程序的共同路障。
在本教程中,我们将逐步向您介绍服务器端的渲染示例。包括围绕着 APIS 交流一些在服务端渲染 React 应用程序的共同障碍。
服务端渲染的优势
可能您的团队谈论到服务端渲染的好处是首先会想到 SEO,但这并不是唯一的潜在好处。
更大的好处如下:服务器端渲染能更快地显示页面。使用服务器端渲染,您的服务器对浏览器进行响应是在您的 HTML 页面可以渲染的时候,因此浏览器可以不用等待所有的 JavaScript 被下载和执行就可以开始渲染。当浏览器下载并执行页面所需的 JavaScript 和其他资源时,不会出现 “白屏” 现象,而 “白屏” 这是在完全有客户端呈现的 React 网站中可能发生的情况。
入门
接下来让我们来看看如何将服务器端渲染添加到一个基本的客户端渲染的使用Babel和Webpack的React应用程序中。我们的应用程序将增加从第三方 API 获取数据的复杂性。我们在GitHub上提供了相关代码,您可以在其中看到完整的示例。
提供的代码中只有一个 React 组件,`hello.js`,这个文件将向 ButterCMS 发出异步请求,并渲染返回的 JSON 列表的博文。ButterCMS 是一个基于API的博客引擎,可供个人使用,因此它非常适合测试现实生活中的用例。启动代码中连接着一个 API token,如果你想使用你自己的 API token 可以使用你的 GitHub 账号登入 ButterCMS。
import React from 'react';
import Butter from 'buttercms'
const butter = Butter('b60a008584313ed21803780bc9208557b3b49fbb');
var Hello = React.createClass({
getInitialState: function() {
return {loaded: false};
},
componentWillMount: function() {
butter.post.list().then((resp) => {
this.setState({
loaded: true,
resp: resp.data
})
});
},
render: function() {
if (this.state.loaded) {
return (
<div>
{this.state.resp.data.map((post) => {
return (
<div key={post.slug}>{post.title}</div>
)
})}
</div>
);
} else {
return <div>Loading...</div>;
}
}
});
export default Hello;
启动器代码中包含以下内容:
- package.json - 依赖项
- Webpack 和 Babel 配置
- index.html - app 的 HTML 文件
- index.js - 加载 React 并渲染 Hello 组件
要使应用运行,请先克隆资源库:
git clone ...
cd ..
安装依赖:
npm install
然后启动服务器:
npm run start
浏览器输入 http://localhost:8000 可以看到这个 app: (这里译者进行补充,package.json 里的 start 命令改为如下:"start": webpack-dev-server --watch)

如果您查看渲染页面的源代码,您将看到发送到浏览器的标记只是一个到 JavaScript 文件的链接。这意味着页面的内容不能保证被搜索引擎和社交媒体平台抓取:

增加服务器端渲染
接下来,我们将实现服务器端渲染,以便将完全生成的HTML发送到浏览器。如果要同时查看所有更改,请查看GitHub上的差异。
To get started, we'll install Express, a Node.js server side application framework:
开始前,让我们安装 Express,一个 Node.js 的服务器端应用程序框架:
npm install express --save
我们要创建一个渲染我们的 React 组件的服务器:
import express from 'express';
import fs from 'fs';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import Hello from './Hello.js';
function handleRender(req, res) {
// 把 Hello 组件渲染成 HTML 字符串
const html = ReactDOMServer.renderToString(<Hello />);
// 加载 index.html 的内容
fs.readFile('./index.html', 'utf8', function (err, data) {
if (err) throw err;
// 把渲染后的 React HTML 插入到 div 中
const document = data.replace(/<div id="app"><\/div>/, `<div id="app">${html}</div>`);
// 把响应传回给客户端
res.send(document);
});
}
const app = express();
// 服务器使用 static 中间件构建 build 路径
app.use('/build', express.static(path.join(__dirname, 'build')));
// 使用我们的 handleRender 中间件处理服务端请求
app.get('*', handleRender);
// 启动服务器
app.listen(3000);
让我们分解下程序看看发生了什么事情...
handleRender 函数处理所有请求。在文件顶部导入的 ReactDOMServer 类提供了将 React 节点渲染成其初始 HTML 的 renderToString() 方法
ReactDOMServer.renderToString(<Hello />);
这将返回 Hello 组件的 HTML ,我们将其注入到 index.html 的 HTML 中,从而生成服务器上页面的完整 HTML 。
const document = data.replace(/<div id="app"><\/div>/,`<div id="app">${html}</div>`);
To start the server, update the start script in package.json and then run npm run start:
要启动服务器,请更新 `package.json` 中的起始脚本,然后运行 npm run start :
"scripts": {
"start": "webpack && babel-node server.js"
},
浏览 http://localhost:3000 查看应用程序。瞧!您的页面现在正在从服务器渲染出来了。但是有个问题,
如果您在浏览器中查看页面源码,您会注意到博客文章仍未包含在回复中。这是怎么回事?如果我们在Chrome中打开网络标签,我们会看到客户端上发生API请求。

虽然我们在服务器上渲染了 React 组件,但是 API 请求在 componentWillMount 中异步生成,并且组件在请求完成之前渲染。所以即使我们已经在服务器上完成渲染,但我们只是完成了部分。事实上,React repo 有一个 issue,超过 100 条评论讨论了这个问题和各种解决方法。
在渲染之前获取数据
要解决这个问题,我们需要在渲染 Hello 组件之前确保 API 请求完成。这意味着要使 API 请求跳出 React 的组件渲染循环,并在渲染组件之前获取数据。我们将逐步介绍这一步,但您可以在GitHub上查看完整的差异。
To move data fetching before rendering, we'll install react-transmit:
要在渲染之前获取数据,我们需安装 react-transmit:
npm install react-transmit --save
React Transmit 给了我们优雅的包装器组件(通常称为“高阶组件”),用于获取在客户端和服务器上工作的数据。
这是我们使用 react-transmit 后的组件的代码:
import React from 'react';
import Butter from 'buttercms'
import Transmit from 'react-transmit';
const butter = Butter('b60a008584313ed21803780bc9208557b3b49fbb');
var Hello = React.createClass({
render: function() {
if (this.props.posts) {
return (
<div>
{this.props.posts.data.map((post) => {
return (
<div key={post.slug}>{post.title}</div>
)
})}
</div>
);
} else {
return <div>Loading...</div>;
}
}
});
export default Transmit.createContainer(Hello, {
// 必须设定 initiallVariables 和 ftagments ,否则渲染时会报错
initialVariables: {},
// 定义的方法名将成为 Transmit props 的名称
fragments: {
posts() {
return butter.post.list().then((resp) => resp.data);
}
}
});
我们已经使用 Transmit.createContainer 将我们的组件包装在一个高级组件中,该组件可以用来获取数据。我们在 React 组件中删除了生命周期方法,因为无需两次获取数据。同时我们把 render 方法中的 state 替换成 props,因为 React Transmit 将数据作为 props 传递给组件。
为了确保服务器在渲染之前获取数据,我们导入 Transmit 并使用 Transmit.renderToString 而不是 ReactDOM.renderToString 方法
import express from 'express';
import fs from 'fs';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import Hello from './Hello.js';
import Transmit from 'react-transmit';
function handleRender(req, res) {
Transmit.renderToString(Hello).then(({reactString, reactData}) => {
fs.readFile('./index.html', 'utf8', function (err, data) {
if (err) throw err;
const document = data.replace(/<div id="app"><\/div>/, `<div id="app">${reactString}</div>`);
const output = Transmit.injectIntoMarkup(document, reactData, ['/build/client.js']);
res.send(document);
});
});
}
const app = express();
// 服务器使用 static 中间件构建 build 路径
app.use('/build', express.static(path.join(__dirname, 'build')));
// 使用我们的 handleRender 中间件处理服务端请求
app.get('*', handleRender);
// 启动服务器
app.listen(3000);
重新启动服务器浏览到 http://localhost:3000。查看页面源代码,您将看到该页面现在完全呈现在服务器上!

更进一步
我们做到了!在服务器上使用 React 可能很棘手,尤其是从 API 获取数据时。幸运的是,React社区正在蓬勃发展,并创造了许多有用的工具。如果您对构建在客户端和服务器上渲染的大型 React 应用程序的框架感兴趣,请查看 Walmart Labs 的 Electrode 或 Next.js。或者如果要在 Ruby 中渲染 React ,请查看 AirBnB 的 Hypernova 。
[译]React 在服务端渲染的实现的更多相关文章
- React 在服务端渲染的实现
原文地址:Server-Side React Rendering 原文作者:Roger Jin 译者:牧云云 React 在服务端渲染的实现 React是最受欢迎的客户端 JavaScript 框架, ...
- React 16 服务端渲染的新特性
React 16 服务端渲染的新特性 React 16 中关于服务端渲染的新特性 快速介绍React 16 服务端渲染的新特性,包括数组.性能.流等 React 16 终于来了!
- react+redux教程(六)redux服务端渲染流程
今天,我们要讲解的是react+redux服务端渲染.个人认为,react击败angular的真正“杀手锏”就是服务端渲染.我们为什么要实现服务端渲染,主要是为了SEO. 例子 例子仍然是官方的计数器 ...
- react服务端渲染(同构)
学习react也有一段时间了,使用react后首页渲染的速度与seo一直不理想.打算研究一下react神奇服务端渲染. react服务端渲染只能使用nodejs做服务端语言实现前后端同构,在后台对re ...
- 详解react/redux的服务端渲染:页面性能与SEO
亟待解决的疑问 为什么服务端渲染首屏渲染快?(对比客户端首屏渲染) react客户端渲染的一大痛点就是首屏渲染速度慢问题,因为react是一个单页面应用,大多数的资源需要在首次渲染前就加载 ...
- 【redux】详解react/redux的服务端渲染:页面性能与SEO
亟待解决的疑问 为什么服务端渲染首屏渲染快?(对比客户端首屏渲染) react客户端渲染的一大痛点就是首屏渲染速度慢问题,因为react是一个单页面应用,大多数的资源需要在首次渲染前就加载 ...
- Vue 爬坑之路(十一)—— 基于 Nuxt.js 实现服务端渲染(SSR)
直接使用 Vue 构建前端单页面应用,页面源码时只有简单的几行 html,这并不利于网站的 SEO,这时候就需要服务端渲染 2016 年 10 月 25 日,zeit.co 背后的团队对外发布了一个 ...
- [Next] 服务端渲染知识补充
渲染 渲染:就是将数据和模版组装成 html 客户端渲染 客户端渲染模式下,服务端把渲染的静态文件给到客户端,客户端拿到服务端发送过来的文件自己跑一遍 js,根据 JS 运行结果,生成相应 DOM,然 ...
- nextjs服务端渲染原理
1. 简单的介绍一下 nextjs是react进行服务端渲染的一个工具,默认以根目录下的pages为渲染路由 比如我在pages目录下创建一个index.js文件,然后export default一个 ...
随机推荐
- 关于web优化(一)
我们所说的web,无非就是html,css(web font, image),JavaScript. HTML优化建议: 1. 尽量不要用table进行布局. 2. 尽量用最新的带有语义的h5标签,这 ...
- sqlserver 将 “用 特定字符 分隔的一个字段” 拆分成多个字段,然后两个表之间数据更新
将源TXT文件sourceFile_table.txt导入数据库,生成新表dbo.sourceFile_table.新增字段lon.lat.shi.xian 源表dbo.sourceFile_tabl ...
- Python基础知识:while循环
1.在循环中使用continue输出1-10之间的奇数 num=0 while num <10: num += 1 if num %2 == 0: #--%--运算符,相除返回余数 contin ...
- January 02nd, 2018 Week 01st Tuesday
I dream my painting, and then I paint my dream. 我梦见我的画,然后我画我的梦. It was a long time after I had a goo ...
- Sudoku(第二次作业)
这里是github 工具清单: 编程语言:C++ 编程IDE:XCode 效能分析工具:XCode 源代码管理平台:Github PSP2.1 PSP2.1 Personal Software Pro ...
- 简易 Token 验证的实现
简易 Token 验证的实现 前言 在我们的服务器和客户端的交互中,由于我们的业务中使用 RESTful API 的形式和客户端交互,而 API 又是无状态的,无法帮助我们识别这一次和上一次的请求由谁 ...
- 使用requests模块保存网络上的图片
import requests url = 'https://www.baidu.com/img/bd_logo1.png' r = requests.get(url=url) with open(' ...
- Java虚拟机4:Java对象创建和对象访问
1.对象创建 Java是一门面向对象的语言,Java程序运行过程中无时无刻都有对象被创建出来.在语言层面上,创建对象(克隆.反序列化)就是一个new关键字而已,但是虚拟机层面上却不是如此.看一下在虚拟 ...
- python 在windows下的 虚拟环境
解决 python 环境问题 windows 下安装 pip install virtualenv virtualenv的基本使用 1.1 创建虚拟环境 virtualenv venv 为环境指定Py ...
- Spring集成MyBatis持久层框架
一.MyBatis介绍 MyBatis 是一款优秀的持久层框架,它支持定制化SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的JDBC代码和手动设置参数以及获取结果集,可以使用简单的XML ...