详解React 在服务端渲染的实现


Posted in Javascript onNovember 16, 2017

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;

启动器代码中包含以下内容:

  1. package.json - 依赖项
  2. Webpack 和 Babel 配置
  3. index.html - app 的 HTML 文件
  4. index.js - 加载 React 并渲染 Hello 组件

要使应用运行,请先克隆资源库:

git clone ...
cd ..

安装依赖:

npm install

然后启动服务器:

npm run start

浏览器输入 http://localhost:8000 可以看到这个 app: (这里译者进行补充,package.json 里的 start 命令改为如下:"start": webpack-dev-server --watch)

详解React 在服务端渲染的实现

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

详解React 在服务端渲染的实现

增加服务器端渲染

接下来,我们将实现服务器端渲染,以便将完全生成的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 在服务端渲染的实现

虽然我们在服务器上渲染了 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 在服务端渲染的实现

更进一步

我们做到了!在服务器上使用 React 可能很棘手,尤其是从 API 获取数据时。幸运的是,React社区正在蓬勃发展,并创造了许多有用的工具。如果您对构建在客户端和服务器上渲染的大型 React 应用程序的框架感兴趣,请查看 Walmart Labs 的 Electrode 或Next.js。或者如果要在 Ruby 中渲染 React ,请查看 AirBnB 的 Hypernova 。

 原文地址:https://css-tricks.com/server-side-react-rendering/

 原文作者:Roger Jin

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
基于JQuery的日期联动实现代码
Feb 24 Javascript
js charAt的使用示例
Feb 18 Javascript
js获取select默认选中的Option并不是当前选中值
May 07 Javascript
JS数组array元素的添加和删除方法代码实例
Jun 01 Javascript
JavaScript中利用各种循环进行遍历的方式总结
Nov 10 Javascript
Knockout自定义绑定创建方法
Dec 26 Javascript
树结构之JavaScript
Jan 24 Javascript
jquery实现图片放大点击切换
Jun 06 jQuery
脚手架vue-cli工程webpack的作用和特点
Sep 29 Javascript
微信JS-SDK updateAppMessageShareData安卓不能自定义分享详解
Mar 29 Javascript
使用 Jest 和 Supertest 进行接口端点测试实例详解
Apr 25 Javascript
关于Node.js中频繁修改代码重启服务器的问题
Oct 15 Javascript
Angular 5.0 来了! 有这些大变化
Nov 15 #Javascript
详解React Native 采用Fetch方式发送跨域POST请求
Nov 15 #Javascript
bootstrap 通过加减按钮实现输入框组功能
Nov 15 #Javascript
layui框架中layer父子页面交互的方法分析
Nov 15 #Javascript
layer实现关闭弹出层刷新父界面功能详解
Nov 15 #Javascript
layui.js实现的表单验证功能示例
Nov 15 #Javascript
javascript函数的节流[throttle]与防抖[debounce]
Nov 15 #Javascript
You might like
PHP实现获取域名的方法小结
2014/11/05 PHP
php模拟登陆的实现方法分析
2015/01/09 PHP
PHP使用curl模拟post上传及接收文件的方法
2016/03/04 PHP
php微信公众号开发之欢迎老朋友
2018/10/20 PHP
基于jQuery的的一个隔行变色,鼠标移动变色的小插件
2010/07/06 Javascript
使用ExtJS技术实现的拖动树结点
2010/08/05 Javascript
使用jquery hover事件实现表格的隔行换色功能示例
2013/09/03 Javascript
js导出table数据到excel即导出为EXCEL文档的方法
2013/10/10 Javascript
客户端js判断文件类型和文件大小即限制上传大小
2013/11/20 Javascript
使用JavaScript实现ajax的实例代码
2016/05/11 Javascript
js判断浏览器是否支持严格模式的方法
2016/10/04 Javascript
Ztree新增角色和编辑角色回显问题的解决
2016/10/25 Javascript
浅谈Angular的$q, defer, promise
2016/12/20 Javascript
详细分析JS函数去抖和节流
2017/12/05 Javascript
通过JS运行机制的角度说说作用域
2019/03/12 Javascript
Angular2实现的秒表及改良版示例
2019/05/10 Javascript
jQuery实现王者荣耀手风琴效果
2020/01/17 jQuery
[01:11]辉夜杯战队访谈宣传片—CDEC.Y
2015/12/26 DOTA
用实例说明python的*args和**kwargs用法
2013/11/01 Python
Python编程中实现迭代器的一些技巧小结
2016/06/21 Python
全面分析Python的优点和缺点
2018/02/07 Python
Python + Flask 实现简单的验证码系统
2019/10/01 Python
python-sys.stdout作为默认函数参数的实现
2020/02/21 Python
Django实现将views.py中的数据传递到前端html页面,并展示
2020/03/16 Python
python使用hdfs3模块对hdfs进行操作详解
2020/06/06 Python
阿迪达斯荷兰官方网站:adidas荷兰
2018/03/16 全球购物
你们项目是如何进行变更控制的
2015/08/26 面试题
计算机应用职专应届生求职信
2013/11/12 职场文书
大学生专科毕业生自我评价
2013/11/17 职场文书
个人党性剖析材料
2014/02/03 职场文书
授权委托书(公民个人适用)
2014/09/19 职场文书
2014年项目经理工作总结
2014/11/24 职场文书
工会经费申请报告
2015/05/15 职场文书
2016婚礼主持词开场白
2015/11/24 职场文书
php微信小程序解包过程实例详解
2021/03/31 PHP
python实现黄金分割法的示例代码
2021/04/28 Python