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

英文原文 :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('/',{
    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'),{
    react: React.renderToString(HelloMessage({name: "John"}))
  })
})

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

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

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

app.get('/name'," + 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.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom