从零开始React服务器渲染

栏目: IOS · Android · 发布时间: 5年前

内容简介:一.前言当我们选择使用Node+React的技术栈开发Web时,React提供了一种优雅的方式实现服务器渲染。使用React实现服务器渲染有以下1.利于SEO:React服务器渲染的方案使你的页面在一开始就有一个HTML DOM结构,方便Google等搜索引擎的爬虫能爬到网页的内容。

一.前言

当我们选择使用Node+React的技术栈开发Web时,React提供了一种优雅的方式实现服务器渲染。使用React实现服务器渲染有以下 好处

1.利于SEO:React服务器渲染的方案使你的页面在一开始就有一个HTML DOM结构,方便Google等搜索引擎的爬虫能爬到网页的内容。

2.提高首屏渲染的速度:服务器直接返回一个填满数据的HTML,而不是在请求了HTML后还需要异步请求首屏数据。

3.前后端都可以使用js

二.神奇的renderToString和renderToStaticMarkup

有两个神奇的React API都可以实现React服务器渲染: renderToStringrenderToStaticMarkup 。renderToString和renderToStaticMarkup的主要作用都是 将React Component转化为HTML的字符串 。这两个函数都属于react-dom(react-dom/server)包,都接受一个React Component参数,返回一个String。

也许你会奇怪为什么会有两个用于服务器渲染的函数,其实这两个函数是有 区别 的:

1.renderToString:将React Component转化为HTML字符串,生成的HTML的DOM会带有额外属性:各个DOM会有 data-react-id 属性,第一个DOM会有 data-checksum 属性。

2.renderToStaticMarkup:同样是将React Component转化为HTML字符串,但是生成HTML的DOM不会有额外属性,从而节省HTML字符串的大小。

下面是一个在服务器端使用renderToStaticMarkup渲染静态页面的例子:

npm包安装:

npm -S install express react react-dom

server.js

var express = require('express');
var app = express();

var React = require('react'),
 ReactDOMServer = require('react-dom/server');

var App = React.createFactory(require('./App'));

app.get('/', function(req, res) {
 var html = ReactDOMServer.renderToStaticMarkup(
 React.DOM.body(
 null,
 React.DOM.div({id: 'root',
 dangerouslySetInnerHTML: {
 __html: ReactDOMServer.renderToStaticMarkup(App())
 }
 })
 )
 );

 res.end(html);
});

app.listen(3000, function() {
 console.log('running on port ' + 3000);
});

App.js

var React = require('react'),
    DOM = React.DOM, div = DOM.div, button = DOM.button, ul = DOM.ul, li = DOM.li

module.exports = React.createClass({
  getInitialState: function() {
   return {
     isSayBye: false
   }
  },
  handleClick: function() {
   this.setState({
     isSayBye: !this.state.isSayBye
   })
  },
  render: function() {
    var content = this.state.isSayBye ? 'Bye' : 'Hello World';
    return div(null,
      div(null, content),
      button({onClick: this.handleClick}, 'switch')
    );
  }
})

运行:

node server.js

结果:

从零开始React服务器渲染

三.动态的React组件

上例的页面中,点击“switch”按钮是没有反应的,这是因为这个页面只是一个静态的HTML页面,没有在客户端渲染React组件并初始化React实例。只有在 初始化React实例后 ,才能更新组件的state和props,初始化React的事件系统,执行虚拟DOM的重新渲染机制,让React组件真正“动”起来。

或许你会奇怪,服务器端已经渲染了一次React组件,如果在客户端中再渲染一次React组件,会不会渲染两次React组件。答案是不会的。秘诀在于 data-react-checksum 属性:

上文有说过,如果使用renderToString渲染组件,会在组件的第一个DOM带有 data-react-checksum 属性,这个属性是通过 adler32算法 算出来:如果两个组件有相同的props和DOM结构时,adler32算法算出的checksum值会一样,有点类似于哈希算法。

当客户端渲染React组件时,首先计算出组件的checksum值,然后检索HTML DOM看看是否存在数值相同的 data-react-checksum 属性,如果存在,则组件只会渲染一次,如果不存在,则会抛出一个warning异常。也就是说,当 服务器端和客户端渲染具有相同的props和相同DOM结构的组件时,该React组件只会渲染一次

在服务器端使用 renderToStaticMarkup 渲染的组件不会带有 data-react-checksum 属性,此时客户端会重新渲染组件,覆盖掉服务器端的组件。因此,当页面不是渲染一个静态的页面时,最好还是使用 renderToString 方法。

上述的客户端渲染React组件的流程图如下:

从零开始React服务器渲染

四.一个完整的例子

下面使用React服务器渲染实现一个简单的计数器。为了简单,本例中不使用redux、react-router框架,尽量排除各种没必要的东西。

项目目录如下:

从零开始React服务器渲染

npm包安装:

npm install -S express react react-dom jsx-loader

webpack.config.js :webpack配置文件,作用是在客户端中可以使用代码模块化和jsx形式的组件编写方式:

var path = require('path');

var assetsPath = path.join(__dirname, "public", "assets");
var serverPath = path.join(__dirname, "server");

module.exports = [
 {
 name: "browser",
 entry: './app/entry.js',
 output: {
 path: assetsPath,
 filename: 'entry.generator.js'
 },
 module: {
        loaders: [ 
            { test: /\.js/, loader: "jsx-loader" }
        ]
    }

},
{
name: "server-side rending",
entry: './server/page.js',
output: {
path: serverPath,
filename: "page.generator.js",
// 使用page.generator.js的是nodejs,所以需要将
// webpack模块转化为CMD模块
library: 'page',
libraryTarget: 'commonjs' 
},
module: {
loaders: [
{ test: /\.js$/, loader: 'jsx-loader' }
 ]
 }
 }
]

app/App.js :根组件 (一个简单的计数器组件),在客户端和服务器端都需要引入使用

var React = require('react');

var App = React.createClass({
 getInitialState: function() {
        return {
            count: this.props.initialCount
        };
    },

    _increment: function() {
        this.setState({ count: this.state.count + 1 });
    },

 render: function() {
 return (
 <div>
 <span>the count is: </span>
<span onClick={this._increment}>{this.state.count}</span>
 </div>
 )
 }
})

module.exports = App;

server/index.js :服务器入口文件:

var express = require('express');
var path = require('path');

var page = require("./page.generator.js").page;

var app = express();
var port = 8082;

app.use(express.static(path.join(__dirname, '..', 'public')));

app.get('/', function(req, res) {
 var props = {
 initialCount: 9
 };
 var html = page(props);
 res.end(html);
});

app.listen(port, function() {
 console.log('Listening on port %d', port);
});

server/page.js :暴露一个根组件转化为字符串的方法

var React = require('react');
var ReactDOMServer = require("react-dom/server");

var App = require('../app/App');

var ReactDOM = require('react-dom');


module.exports = function(props) {
 
 var content = ReactDOMServer.renderToString(
 <App initialCount={props.initialCount}></App>
 );

 var propsScript = 'var APP_PROPS = ' + JSON.stringify(props);

 var html = ReactDOMServer.renderToStaticMarkup(
 <html>
 <head>
 </head>
 <body>
 <div id="root" dangerouslySetInnerHTML={
 {__html: content}
 } />
 <script dangerouslySetInnerHTML={
 {__html: propsScript}
 }></script>
 <script src={"assets/entry.generator.js"}></script>
 </body>
 </html>
 );

 return html;
}

为了让服务器端和客户端的props一致 ,将一个服务器生成的首屏props赋给客户端的全局变量 APP_PROPS ,在客户端初始化根组件时使用这个APP_PROPS根组件的props。

app/entry.js :客户端入口文件,用于在客户端渲染根组件,别忘了使用在服务器端写入的 APP_PROPS 初始化根组件的props

var React = require('react'),
 ReactDOM = require('react-dom'),
 App = require('./App');

var APP_PROPS = window.APP_PROPS || {};

ReactDOM.render(
 <App initialCount={APP_PROPS.initialCount}/>,
 document.getElementById('root')
);

源代码放在github上,懒得复制粘贴搭建项目的同学可以猛戳 这里

github上还有其他的服务器渲染的例子,有兴趣的同学可以参考参考:

1. 无webpack无jsx版本

2. 使用webpack版本

参考文章:

1. Rendering React Components on the Server

2. 一看就懂的 React Server Rendering(Isomorphic JavaScript)入門教學

3. Clientside react-script overrides serverside rendered props

4. React直出实现与原理

5. React Server Side Rendering 解决 SPA 应用的 SEO 问题

6. Server-Side Rendering with React + React-Router


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Implementing Responsive Design

Implementing Responsive Design

Tim Kadlec / New Riders / 2012-7-31 / GBP 27.99

New devices and platforms emerge daily. Browsers iterate at a remarkable pace. Faced with this volatile landscape we can either struggle for control or we can embrace the inherent flexibility of the w......一起来看看 《Implementing Responsive Design》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具