详解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 过滤not()与filter()实例代码
May 10 Javascript
javascript创建数组之联合数组的使用方法示例
Dec 26 Javascript
input输入框鼠标焦点提示信息
Mar 17 Javascript
JS基于cookie实现来宾统计记录访客信息的方法
Aug 04 Javascript
七夕情人节丘比特射箭小游戏
Aug 20 Javascript
Three.js学习之网格
Aug 10 Javascript
Angularjs实现mvvm式的选项卡示例代码
Sep 08 Javascript
利用JS轻松实现获取表单数据
Dec 06 Javascript
vue.draggable实现表格拖拽排序效果
Dec 01 Javascript
如何通过shell脚本自动生成vue文件详解
Sep 10 Javascript
Vue filter 过滤当前时间 实现实时更新效果
Dec 20 Javascript
高性能js数组去重(12种方法,史上最全)
Dec 21 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
新版mysql+apache+php Linux安装指南
2006/10/09 PHP
用文本文件制作留言板提示(下)
2006/10/09 PHP
vBulletin HACK----关于排版的两个HACK
2006/10/09 PHP
php 日期和时间的处理-郑阿奇(续)
2011/07/04 PHP
Laravel5.7 Eloquent ORM快速入门详解
2019/04/12 PHP
用jscript实现新建word文档
2007/06/15 Javascript
javascript 打印内容方法小结
2009/11/04 Javascript
通过百度地图获取公交线路的站点坐标的js代码
2012/05/11 Javascript
Javascript基础教程之for循环
2015/01/18 Javascript
JavaScript控制table某列不显示的方法
2015/03/16 Javascript
js改变style样式和css样式的简单实例
2016/06/28 Javascript
layer弹出层中H5播放器全屏出错的解决方法
2017/02/21 Javascript
vue使用axios跨域请求数据问题详解
2017/10/18 Javascript
JavaScript内置对象math,global功能与用法实例分析
2019/06/10 Javascript
在JavaScript中使用严格模式(Strict Mode)
2019/06/13 Javascript
如何阻止小程序遮罩层下方图层滚动
2019/09/05 Javascript
Python 反转字符串(reverse)的方法小结
2018/02/20 Python
python获取代码运行时间的实例代码
2018/06/11 Python
解决pyinstaller打包pyqt5的问题
2019/01/08 Python
Python3 无重复字符的最长子串的实现
2019/10/08 Python
Python实现基于socket的udp传输与接收功能详解
2019/11/15 Python
tensorflow 实现从checkpoint中获取graph信息
2020/02/10 Python
pytorch学习教程之自定义数据集
2020/11/10 Python
Python+Opencv实现把图片、视频互转的示例
2020/12/17 Python
css3制作彩色边线3d立体按钮的示例(css3按钮)
2014/05/06 HTML / CSS
HTML5里autofocus自动聚焦属性使用介绍
2016/06/22 HTML / CSS
泰国第一的化妆品网站:Konvy
2018/02/25 全球购物
心理健康教育心得体会
2013/12/29 职场文书
销售人员求职的自我评价分享
2014/03/15 职场文书
葛优非诚勿扰搞笑征婚台词
2014/03/17 职场文书
2014年学习部工作总结
2014/11/12 职场文书
2015年双拥工作总结
2015/04/08 职场文书
酒店客房服务员岗位职责
2015/04/09 职场文书
2015年基层党支部工作总结
2015/05/21 职场文书
教你用Python写一个植物大战僵尸小游戏
2021/04/25 Python
天谕手游15杯全调酒配方和调酒券的获得方式
2022/04/06 其他游戏