您的位置:首页 > 移动开发

自译: 如何使用服务器端渲染构建快速加载的React apps

2016-11-11 19:01 429 查看
英文原文 :How to build React apps that load quickly using server side rendering

我们知道客户端框架非常优秀,他能够帮助我们构建用户们喜爱的交互式的快速的web应用。

不幸的是他并不总是那么完美,也有一些缺点。最大的缺点就是他的初始化加载速度。客户端框架会从后台获取很少的html,但是他会获取大量的JavaScript,那么客户端就要请求并等待将要被渲染的这些数据,最终他们在用户机上被做响应的数据处理并完成渲染。

在另一方面,传统的web站点都是在服务器端渲染一切,并且一旦html被渲染完成并发送客户端,那么客户端页面会立刻得以展示,并等待用户的下一步操作。

更甚者,大多数的服务器端渲染页面速度远快于客户端渲染,因此,这种方式初始化加载非常迅速。

React 的解决方案

自然而然的,你想拥有上述两者的全部优点,高效的初始化加载,交互丰富的应用。那么React能够帮助你做到这些。(传统web站点都是服务器端渲染成完整的html页面,发送到客户端,性能比较高,mvc式的web app在客户端是还要做处理的,只是去server拿数据)

这里说一下他的工作方式。首先他有能力在服务端去渲染任何组件,包括他的数据,这个结果将会是一些html并被发送到浏览器。一旦这个html被展示在用户浏览器上,React 将会做一些本地“计算”,它的“聪明”算法,将会检查当前将要渲染的结果和当前已渲染完成的页面的相同之处, 那么这样做的结果就是在渲染的时候并不需要做一些没必要的改变。它仅仅将要添加一些必要的事件处理。那么为什么他会更快呢? 我们不是几乎在做跟客户端一样的事情么? 是的,是“几乎”!

首先,一旦服务器响应了对浏览器的请求,用户立马看到页面,所以我们感到速度比之前快。

其次,因为React会识别且不操作并没有改变的DOM,因为这是在渲染中最慢的一部分。(在浏览器中操作DOM,扯一下reactjs语法中的key属性,就是标注DOM结点的唯一性,算法判断每个标注是否需要重新渲染,有稍微变动,那么整个由key标注的DOM将被重新渲染)

另外,可以节约客户端请求,因为所有已接受的数据都已经渲染在页面上,React并不需要从服务器端再做请求。

是不是有可能处于loading状态的页面已经展示在客户端,但是用户不能和页面进行交互,因为相应的时间处理器尚未被添加?

理论上它是有可能的。然而运用这种(React)技术我们能够避免所有的高开销的操作,并且导致不仅响应速度加快,而且添加时间处理器也变得非常快速。

结果。你的应用程序总是交互性很强,你的用户并不会注意到任何问题。

举例

说多了太累,让我们看看在实践中他到底是如何工作的,我们的首个示例将会非常简单。我们想要在用户点击的时候展示一个Hello消息,并显示提示。

我们的示例将会使用NodeJS作为服务端,但是我们现在看到的一些同样能够应用于其他的平台和语言,比如PHP,Ruby,Python,Java or .NET。

我们需要以下 node package

$ npm install babel react react-dom express jade


我们将要为我们的示例使用 express 和 jade 去创建服务端, react 和 react-dom 包 将会提供对React组件的服务端渲染的功能。

babel 包 能够允许我们在Node中直接去加载JSX语法组件,例如 require(‘some-component.jsx’) 或着require(‘some-component.js’).

babel 事实上比那强大的多, 它能够允许你使用ES6的特性。

应用程序在下面所示中将会仅仅包含三个文件:

public/components.js
views/index.jade
app.js


component.js 将会包含我们的React 组件。index.jade将会提供基本的模版文件并会加载其全部JavaScript 。app.js将会是我们的Node服务器。

让我们来看看index.jade的模版包含:

doctype
html
head
title React Server Side Rendering Example
body
div(id='react-root')!= react

script(src='https://fb.me/react-0.14.0.js')
script(src='https://fb.me/react-dom-0.14.0.js')
script(src='https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js')

script(src='/components.js', type='text/babel')


div(id=’react-root’)!= react 他的目的是包含React根组件,这个react 变量将会包含某些React组件被服务器端渲染之后的HTML。

前两个被包含的文件是React 本身,如果你想在你的组件中使用JSX,那么你就需要包含Babel。

最后一行是我们的组件,我们通过设置text/babel要让babel知道他应该去会处理这种文件。这个文件提供了最基本的HTML 结构并且加载了所有的JavaScript和你需要的Ract组件。

让我们现在来看看我们的简单服务器:

require('babel/register')

var express = require('express')
, app = express()
, React = require('react')
, ReactDOM = require('react-dom/server')
, components = require('./public/components.js')

var HelloMessage = React.createFactory(components.HelloMessage)

app.engine('jade', require('jade').__express)
app.set('view engine', 'jade')

app.use(express.static(__dirname + '/public'))

app.get('/', function(req, res){
res.render('index', {
react: ReactDOM.renderToString(HelloMessage({name: "John"}))
})
})

app.listen(3000, function() {
console.log('Listening on port 3000...')
})


在你发起的所有快速应用程序中,大多数的文件都是这种方式。

没有什么特别的。然而有一些行需要你的注意。

第一行:

require('babel/register')


加载Babel到你的组件。通过这,你可以直接require其他包含JSX的组件到你当前组件中, 接着他们被转化为JavaScript,就像下面两行。

var components = require('./public/components.js')

var HelloMessage = React.createFactory(components.HelloMessage)


第一行加载用JSX写的React组件。接着React.createFactory创建了一个函数,它能够创建HelloMessage组件。

app.get('/', function(req, res){
res.render('index', {
react: ReactDOM.renderToString(HelloMessage({name: "John"}))
})
})


这就是React组件将被渲染的部分,接着一个页面被渲染,并发送到浏览器。

首先一个新的具有那么属性为john的HelloMessage 组件被创建,接着使用React.renderToString 我们将组件渲染成HTML。

需要牢记的是组件仅仅被渲染,但是并没有被挂载,所以所有和挂载相关的方法都没有被调用。

组件被创建之后,我们传递他的HTML到index模版, 正如你之前所看到的(代码)样子。

我们的组件看起来是这个样子:

var isNode = typeof module !== 'undefined' && module.exports
, React = isNode ? require('react') : window.React
, ReactDOM = isNode ? require('react') : window.ReactDOM

var HelloMessage = React.createClass({
handleClick: function () {
alert('You clicked!')
},

render: function() {
return <div onClick={this.handleClick}>Hello {this.props.name}</div>
}
})

if (isNode) {
exports.HelloMessage = HelloMessage
} else {
ReactDOM.render(<HelloMessage name="John" />, document.getElementById('react-root'))
}


正如你所看到的,它看起来很像其他组件,使用JSX,除了开头和结尾。这里是你应该所关注的去使你的组件在浏览器和Node中。

高级示例: 加载服务端数据

真正的web apps 通常做的比你之前看到的多的多,他们经常需要和服务器交互,并从中加载数据。

然而,我们不想让它在服务器端渲染时发生(交互) 。让我们对已经存在的应用程序做一些小小的改变,首先,模版现在将会加载jQuery, 我们仅仅用它来从后台请求数据。

doctype
html
head
title React Server Side Rendering Example
body
div(id='react-root')!= react

script(src='https://fb.me/react-0.14.0.js')
script(src='https://fb.me/react-dom-0.14.0.js')
script(src='https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js')
script(src='http://code.jquery.com/jquery-2.1.3.js')

script(src='/components.js', type='text/babel')


我们的服务器(下面文件)将必须提供一个请求路径。

require('babel/register')

var express = require('express')
, app = express()
, React = require('react')
, ReactDOM = require('react-dom/server')
, components = require('./public/components.js')

var HelloMessage = React.createFactory(components.HelloMessage)

app.engine('jade', require('jade').__express)
app.set('view engine', 'jade')

app.use(express.static(__dirname + '/public'))

app.get('/', function(req, res){
res.render('index', {
react: React.renderToString(HelloMessage({name: "John"}))
})
})

app.get('/name', function(req, res){
res.send("Paul, " + new Date().toString())
})

app.listen(3000, function() {
console.log('Listening on port 3000...')
})


和上个样例的不同是下面三行:

app.get('/name', function(req, res){
res.send("Paul, " + new Date().toString())
})


他做的所有事是当请求/name ,将会返回一个带有当前日期的名为Paul的信息。让我们看看这个最有趣也是我们app最重要的一部分——这个React组件

var isNode = typeof module !== 'undefined' && module.exports
, React = isNode ? require('react') : window.React
, ReactDOM = isNode ? require('react-dom') : window.ReactDOM

var HelloMessage = React.createClass({
getInitialState: function () {
return {}
},

loadServerData: function() {
$.get('/name', function(result) {
if (this.isMounted()) {
this.setState({name: result})
}
}.bind(this))
},

componentDidMount: function () {
this.intervalID = setInterval(this.loadServerData, 3000)
},

componentWillUnmount: function() {
clearInterval(this.intervalID)
},

handleClick: function () {
alert('You clicked!')
},

render: function() {
var name = this.state.name ? this.state.name : this.props.name
return <div onClick={this.handleClick}>Hello {name}</div>
}
})

if (isNode) {
exports.HelloMessage = HelloMessage
} else {
ReactDOM.render(<HelloMessage name="John" />, document.getElementById('react-root'))
}


除了添加了四个小方法,其它的都和之前的相同。

getInitialState: function () {
return {}
},

loadServerData: function() {
$.get('/name', function(result) {
if (this.isMounted()) {
this.setState({name: result})
}
}.bind(this))
},

componentDidMount: function () {
this.intervalID = setInterval(this.loadServerData, 3000)
},

componentWillUnmount: function() {
clearInterval(this.intervalID)
},


一旦组件被挂载,那么每3秒它就会从/name请求获得一个 名字并展示它。

componentDidMount 和componentWillUnmount 这两个方法在组件被渲染的时候从来不会被调用,只有在被挂载之后,所以这两个方法和loadServerData 方法也在渲染时不会被调用。

这三个方法仅仅在被挂载时被执行,但这仅仅会发生在浏览器中

正如你所看到的,把那些仅仅在发生在浏览器中的那部分逻辑分离出来放在服务端处理,并且仍然能够保持代码的完整性,并且它仍然能工作!

接下来呢?

你已经学会了如何用在服务器端端渲染的方法来创建可以快速加载的React 应用程序。然而我的例子仅仅使用了NodeJS。

如果你想使用其它的技术(像 PHP, .NET Ruby Python or Java),你仍然能从React和服务器端渲染的技术中中获益,这将是你下一步要探究的。

另外,我直接在浏览器中使用JSX,幸亏Babel,但仍会降低“表现力”。对于生产环境中的app在JavaScript发送到浏览器之前转换成JavaScript将会变得更快。

I am sure that you can find out how to do that in your favorite language and web framework.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息