第12章 服务端渲染

想让搜索引擎抓取到你的站点,服务端渲染这一步不可或缺,服务端渲染还可以提升站点的性能,因为在加载JavaScript脚本的同时,浏览器就可以进行页面渲染。

React的虚拟DOM是其可被用于服务端渲染的关键。首先每个React Component 在虚拟DOM中完成渲染,然后React通过虚拟DOM来更新浏览器DOM中产生变化的那一部分,虚拟DOM作为内存中的DOM表现,为React在Node.js这类非浏览器环境下的运行提供了可能,React可以从虚拟DOM中生成一个字符串。而不是更新真正的DOM,这使得我们可以在客户端和服务端使用同一个React Component。

React 提供了两个可用于服务端渲染组件的函数:React.renderToString 和React.renderToStaticMarkup。

在设计用于服务端渲染的ReactComponent时需要有预见性,考虑以下方面。

  • 选取最优的渲染函数。
  • 如何支持组件的异步状态。
  • 如何将应用的初始化状态传递到客户端。
  • 哪些生命周期函数可以用于服务端的渲染。
  • 如何为应用提供同构路由支持。
  • 单例、实例以及上下文的用法。

渲染函数

在服务端渲染React Component时,无法使用标准的React.render方法,因为服务端不存在DOM。React提供了两个渲染的函数,它们支持标准的React Component生命周期的一个子集。因而能够实现服务端渲染。

React.renderToString

React.renderToString是两个服务端渲染函数中的一个,也是开发主要使用的一个函数,和React.render不同,该函数去掉了用于表示渲染位置的参数。取而代之,该函数只返回一个字符串,这是一个快速的同步(阻塞式)函数,非常快。

var MyComponent = React.createClass({render:fucniton(){return <div> Hello World!</div>;}
});
var world= React.renderToString (<MyComponent/>);//这个示例返回一个单行并且格式化的输出
<divdata-reactid=".fgvrzhg2yo"data-ract-checksum="-1663559667">Hello World!
</div>

你会注意到,React为这个<div>元素添加了两个data前缀的属性。在浏览器环境下,React使用data-reactid区分DOM节点。这也是每当组件的state及props发生变化时,React都可以精准的跟新制定DOM节点的原因。

data-react-checksum仅仅存在于服务端。顾名思义,它是已创建DOM和校验和。这准许React在客户端服用与服务端结构上相同点的DOM结构。该属性只会添加到根元素上。

React.renderToStaticMarkup

React.renderToStaticMarkup是第二个服务端渲染函数,除了不会包含React的data属性外,它和React.renderToString没有区别。

varMyComponent=React.createClass({render:function(){return<div>Hello World!</div>;}
});
varworld= React.renderToStaticMarkup(<MyCompoent/>);//单行输出
<div>HelloWorld!</div>

用React.renderToString还是React.renderToStaticMarkup

每个渲染函数都有自己的用途,所以你必须明确自己的需求,再去决定使用哪个渲染函数。当且仅当你不打算在客户端渲染这个React Component时,才应该选择使用React.renderToStaticMarkup函数。

下面有一些示例:

  • 生成HTML电子邮件
  • 通过HTML到PDF的转化来生成PDF。
  • 组件测试。

大多数情况下,我们都会选择使用React.renderToString。这将准许React使用data-react-checksum在客户端更迅速的初始化同一个React Component。因为React可以重用服务端提供的 DOM,所以它可以跳过生成DOM节点以及把他们挂载到文档中这两个昂贵的进程。对于复杂些的站点,这样做就会显著的减少加载时间,用户可以更快的与站点进行交互。

确保React Component能够在服务端和客户端准确的渲染出一致的结构是很重要的。如果data-react-checksum不匹配,React会舍弃服务端提供的DOM,然后生成新的DOM节点,并且将它们更新到文档中。此时,React也不再拥有服务端渲染带来的各种性能上的优势。

服务端组件生命周期

一旦渲染为字符串,组件就会只调用位于render之前的组件生命周期方法,需要指出,componentDidMount和componentWillUnmount不会在服务端渲染过程中被调用,而componentWillMount在两种渲染方式下均有效。

当新建一个组件时,你需要考虑到它可能即在服务端又在客户端进行渲染。这一点在创建事件监听器时尤为重要,因为并不存在一个生命周期方法会通知我们React Component是否已经走完了整个生命周期。

在componentWillMount内注册的所有事件监听器及定时器都可能潜在的导致服务端内存泄漏。

最佳做法是只在componentDidMount内部创建事件监听器及定时器,然后在componentWilUnmount内清除这两者。

设计组件

服务端渲染时,请务必慎重考虑如何将组件的state传递到客户端,以充分利用服务端渲染的优势。在设计服务端渲染组件时,要时刻记得这一点。

在设计React Component时,需要保证同一个props传递到组件中,总会输出相同的初始渲染结果。坚持这样做将会提升组件的可测试性,并且可以保证组件在服务端和客户端渲染结果的一致性。充分利用服务端渲染的性能优势十分重要。

我们假设现在需要一个组件,它可以打印一个随机数。一个棘手问题是组件每次输出的结果总是不一致。如果组件在服务端而不是客户端进行渲染,checksum将会失效。

var MyComponent =React.createClass({render:dunction(){return <div>{Math.random()}</div>;}
});
var result = React.renderToStaticmarkup(<MyComponent/>);
var result2 = REact.renderToStaticMarkup(<MyComponent/>);//result
<div>0.5820949131157249</div>//result2
<div>0.420401582631672</div>

如果你打算重构它,组件将会通过props来接收一个随机数。然后,将props传递到客户端用于渲染。

var MyComponent= React.createClass({render :function(){retrun<div>{this.props.number}</div>}
});var num=Math.random();
//服务端
React.renderToString(<MyComponentnumber={num}/);//将num传递到客户端
React.render(<MyComponentnumber ={num}/>,document.body);

有多种方式可以将服务端的props传递到客户端。

最简单的方式之一是通过JavaScript对象将初始的props值传递到客户端。

<!DOCTYPEhtml>
<html><head><title>Example</title><!--bundle 包括MyComponent、React等--><script type =“text/javascript"src="bundle.js"></script></head><body><!--服务端渲染MyComponent的结果--><div data-reactid=".fgvrzhg2yo" data-react-checksum="-1663559667">0.5820949131157249</div><!--注入初始props,供服务端使用--><script type="text/javascript">var initialProps = {"num":0.5820949131157249};</script><!--使用服务端初始props--><script type-"text/javasript">var num = initialProps.num;React.render(<MyComponent number={num}/>, document.body);</script></body></html>

异步状态

很多应用需要从数据库或者网络服务这类远程数据源中读取数据,在客户端,这不是问题,在等待异步数据返回的时候,React Component可以展示一个加载图标。在服务端,React 无法直接复制该方案,因为render函数是同步的。为了使用异步数据,首先需要抓取数据,然后再渲染时将数据传递到组件中。

示例:

1、你可能需要从异步的store中转区用户记录;2、抓取到用户记录后,考虑到SEO以及性能等因素,需要在服务端渲染组件的状态;3、你需要让组件监听在客户端的变化,然后重新渲染

问题:React.renderToString是同步的,所以没有办法使用组件的任何一个生命周期方法,来抓区异步的数据

解决方案:使用statics函数来抓取异步数据,然后把数据传递到组件中用于渲染。将initialState作为props值传递到客户端。使用组件生命周期方法来监听变化,然后使用同一个statics函数更新状态。

var Username= React.createClass({statics:{getAsyncState:function()(props,setState){User.findById(props.userId).then(function(user){setState({user:user});}).catch(function(error){setState({error:error});});}},//客户端和服务器componentWillMount:function(){if(this.props.initialState){this.setState(this.props.initialState);}},//仅客户端componentDidMount:function(){//如果props中没有,则获取一部stateif(!this.props.initialState){this.updateAsynState();}//监听change事件User.on('change',this.updateAsyncState);},//仅客户端componentWillUnmount:funtion(){//停止监听change事件User.off(‘change’,this.undateAsyncState);},updateAsyncState:function(){//访问示例中的静态函数this.constructor.getAsyncState(this.props,this.setstate);},render:funciton(){if(this.state.error){return <div>{this.state.error.message}</div>;}if(!this.state.usr){retrun<div>Loading...</div>}return <div>{this.state.user.username}</div>;}
});//在服务器端渲染
var props={userId:123//也可以通过路由传递
};
Username.getAsyncState(props,funciton(initialState){props[initialState]=initialState;var result =React.renderToString(Username(props));//使用initialState将结果传递到客户端
});

上述解决方案中,预先抓取到异步数据这一步仅在服务端是必须的。在客户端,只有初次渲染时需要查找服务端所传递的initialState。后续客户端上的路由变化(比如HTML5,pushState或者fragment change)都会忽略掉服务端所有的initialState。同时,在抓取数据时最好加载文案信息。

同构路由

对于任意一个完整的应用来说,路由都至关重要。为了在服务端渲染出拥有路由的React应用,你必须确保路由系统支持无DOM渲染。

抓取异步数据是路由系统及其控制器的职责。我们假设一个深度嵌套的组件需要一些异步的数据。如果SEO需要这些数据,那么抓取数据的职责应该被提升至路由控制器,并且这些数据应该被传递到嵌套组件的最内层。如果不用考虑SEO,那么在客户端的componentDidMount方法内抓取数据是没问题的。这与传统的Ajax加载数据方式类似。

考虑一个React同构路由解决方案时,需确保它具有异步状态支持,或者可以轻易地更改以支持异步状态。理想情况下,你也会倾向于使用路由系统来控制,将initialState传递到客户端。

单例、实例及上下文

在浏览器端,你的应用如同包裹在独立的气泡中一样。每个实例之间的状态不会混在一起,因为每个实例通常存在于不同的计算机或者同一台计算机的不同沙箱之中。这使得我们可以在应用架构中轻松地使用单例模式。

当你开始迁移代码并在服务端运行时,你必须要小心,因为可能存在同一应用的多个实例在相同的作用域内同时运行的情况。有可能出现应用的两个实例都去更改单例状态的情况,这会导致异常的行为发生。

React渲染是同步的,所以你可以重置之前使用过的所有单例,而后在服务端渲染你的应用。如果异步状态需要使用单例,则又会遇到问题。同样,在渲染过程中使用抓取到的异步状态时,也需要考虑到这一点。

尽管可以在渲染前重置之前使用过的单例,但是在隔离的环境下运行你的应用总是有好处的。Contextify之类的包准许你在服务端彼此隔离地运行代码。这与客户端使用webworkers类似。Contextify通过将应用代码运行在一个隔离的Node.js V8实例中来工作。一旦加载完代码,你就可以调用环境中的所有函数。这种方法可以让你随意地使用单例模式,而不用考虑性能上的花销,因为每次请求都对应一个全新的Node.js V8实例。

React的核心开发小组不鼓励在组件树中传递上下文和实例。这种做法会降低组件的可移植性,并且应用内组件依赖的更改会对层级上的所有组件产生连动式的影响。这转而增加了应用的复杂性,而随着应用的增长,应用的可维护性也会降低。

当决定使用单例或者实例来控制你的上下文时,需要对两者权衡舍去。在选择一个方法之前,你需要估算出详细的需求,还需要考虑你所使用的第三方类库是如何架构的。

总结

服务端渲染是构建搜索引擎优化的Web站点和Web应用时的重要部分。React支持在服务端和客户端浏览器中渲染相同的React component。要有效地做到这一点,你需要保证整个应用都使用这一架构方式以支持服务端渲染。

第13章 周边类库

围绕着Ract,facebook还开发了一系列的前端工具。在你的React项目中,这些工具不是非用不可的,不过它们确实可以和React一起完美的工作。例如:

  • Jest
  • Immutable.js
  • Flux

Jest

Jest是Factbook开发的一个测试运行工具。它基于Jasmine测试框架提供相近的方式,使用大家熟悉的类似于expect(value).to(other)的断言。它提供了默认的模拟行为,会自动模拟require()返回的CommonJS模块。让现有的代码变成可测试的。它使用了模拟的DOM API ,同时通过小巧的Node.js命令行工具进行运行,缩短每次测试运行的时间。

page 108~112

Immutable.js

不可以变数据结构(Immutable Data Structures)中的数据是不允许修改的。相反,如果数据需要改变,它们会返回原始数据的一个经过修改的拷贝。React跟Flux可以很好的结合不可变数据结构,带来代码的简洁和性能的提升。

Immutable.js提供了多个数据结构,可以有原生的JavaScript数据结构构造而成,在需要的时候,也可以转会原生的JavaSctipt数据结构。

immutable.Map

Immutable.Map可作为常规JavaScript对象的替代者来使用:

var question = Immutable.Map({desctiption:'who is your favorite superhero?'});//使用.get从Map中取值
question.get('desctiption');//通过.set更新值时返回一个新的对象
//原始对象保持不变
question2 = question.set('desctiption', 'Who is your favorite comicbook hero?');//使用.merge合并两个对象得到第三个对象
//同样原来的对象没有任何变化
var title = {title : 'Question #1'};
var question3 = question.merge(question2,title);
question3.toObject();//{title: 'Question #1',desctiption":'who is your favorite comicbook hero'}

Immutable.Vector

可以使用Immutable.Vector代替数组:

var options = Immutable.Vector('Superman', 'Batman');
var option2 = options.push('Spiderman');
option2.toArray(); //['Superman','Batman','Spiderman']

你还可以对这些数据结构进行嵌套:

var options = Immutable.Vector('Superman', 'Batman');
var question = Immutable.Map({description : 'who is your favorite superhero?',options : options
});

Immutable.js还有更丰富的特性,你可以到immutable-js上获取更多的相关信息。

Flux

Flux是Facebook在发布React时发布的一种模式。它显著的特性是严格的单向数据流。

Facebook在GitHub发布了一份关于实习那Flux的参考,可以通过flux访问到。

Flux包含了三个重要的组件

  • Dispatcher
  • Store
  • View

下图清晰地展示了如何将这些部件组合到一起:

Flux没有强制的依赖,你可以任意选取自己需要的模块。

关于Flux更详细的讨论见第16章。

React-引领未来的用户界面开发框架-读书笔记(六)相关推荐

  1. 读书笔记《React引领未来的用户界面开发框架》

    入门react,感觉这本书只是简单说了一些相关概念知识,并没有很详尽地展开论述,看了,做个总结笔记.

  2. React-引领未来的用户界面开发框架-读书笔记(五)

    第11章 性能优化 Reactde Dom diff算法使我们能够在任意时间点高效地重新绘制整个用户界面,并保证最小程度的DOM改变,然而,也存在需要对组件进行细致优化的情况,这时就需要渲染一个新的D ...

  3. React-引领未来的用户界面开发框架-读书笔记(二)

    第4章 数据流 由于react的数据流向是单向的(其父节点传递到子节点), 因此组件是简单且易于把握的(它们只需要从父节点获取props渲染即可) 假如顶层组件的某个prop改变了,react会递归地 ...

  4. React-引领未来的用户界面开发框架-读书笔记(一)

    这本书的主要内容都是以react v0.12为主,ES5语法,笔记中将会使用react v0.14和RS6. 第1章 react简介 1.本质上是一个状态机,它以精简的模型管理复杂的随着时间而变化的状 ...

  5. React-引领未来的用户界面开发框架-读书笔记(八)

    第16章 架构模式 React主要功能在于渲染HTML.可以将其看成是MVC中的V,它不会影响到组件中直接调用AJAX请求之类的操作: var TakeSurvey=React.CreateClass ...

  6. React-引领未来的用户界面开发框架-读书笔记(七)

    第14章 开发工具 React使用了若干的抽象层来帮助你更轻松地开发组件.推导程序状态.然而,在调试.构建及分发应用时,这样设计就会产生负面影响了. 幸运的是,我们拥有一些非常好的开发工具能在开发及构 ...

  7. React-引领未来的用户界面开发框架-读书笔记(四)

    第10章 动画 动画可以让用户体验变得更加流畅自然,而React的TransitionGroup插件配合CSS3可以让我们在项目中整合动画效果的变得易如反掌. 通常情况下,浏览器中的动画都拥有一套极其 ...

  8. React-引领未来的用户界面开发框架-读书笔记(三)

    第8章 DOM操作 多数情况下,React的虚拟DOM足以用来创建你想要的用户体验,而根本不需要直接操作底层真实的DOM.然而也有一些例外.最常见的场景包括:需要与一个没有使用React的第三方类库进 ...

  9. 读书笔记《React:引领未来的用户界面开发框架》

    <React:引领未来的用户界面开发框架>(GitHub 附demo版) 1.Component的创建与复合 1.1 React简介 背景介绍,全书概览 1.本质上是一个状态机,它以精简的 ...

最新文章

  1. App项目内存优化计划
  2. Android Message解析
  3. SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转
  4. 用拦截器+注解+cookie进行简单限流访问案例
  5. 中班机器人教室设计方案_奇思妙想一起玩,机器人来了安格利亚东郡生态幼儿园亲子活动...
  6. 计算机与人脑_类脑计算机:一种新型的计算系统
  7. 听说,他用报表关联数据库表,运维效率提升70%?
  8. Python爬取并简单分析2024年普通高校招生专业(专业类)选考科目要求
  9. python格式化字符串_Python字符串格式化问题:%、format()与f-strings
  10. 三目(三元)运算符??::的形式
  11. 处女座的训练(贪心)
  12. 远程拨号换IP的方法
  13. 转:firefox os 框架解析
  14. c语言中的正弦函数与余弦函数
  15. ibmx340服务器硬盘,IBM3850安装操作系统
  16. 使用系统自带计算器进行二进制运算
  17. SwfitUI之Published
  18. 管家婆服务器端口修改,更改sql数据库端口
  19. 蓝桥杯算法训练——逗志凡的危机——动态规划+递归
  20. 这款基于 Core ML 的开源二次元神器,可以深入学习一下

热门文章

  1. HangFire循环作业中作业因执行时间太长未完成新作业开启导致重复数据的问题...
  2. ASP.NET Web API中实现版本
  3. 02Prism WPF 入门实战 - 建项
  4. 微软面向初学者的机器学习课程:1.4-机器学习技术
  5. Istio 1.9 发布——重点改善 Istio 的 Day2 操作
  6. asp.net core 使用 TestServer 来做集成测试
  7. asp.net core监控—引入Prometheus(二)
  8. 做权限认证,还不了解IdentityServer4?不二话,赶紧拥抱吧,.NET Core官方推荐!...
  9. TIOBE 8 月榜单:C 力压 Java 夺得第一,Java掉了1.6 个点
  10. ​设计模式之装饰模式