3 「武装思想」- React SSR 根本原理

导读

本节标题:「武装思想」- React SSR 根本原理

本节主旨:全面分析 react ssr 技术的本质,并对对虚拟 dom、同构应用具体流程、双端对比机制进行说明。

武装思想

在动手搭建应用骨架前我们要先了解下react ssr 最根本的原理,这是整个技术架构实现的基础,也可以说是基石,可以理解为我们盖房子的地基。

如果你对一些多技术感觉到陌生或者看过很多次仍然会忘记时,主要原因要么是用的少,要么就是不理解其中的原理,导致印象不够深刻。

深入理解原理对于我们的开发和创新(自己造轮子)有很大的帮助,原理可以一步一步的带你走正确的路。

或许你之前觉得react ssr 这个技术很高大上,学起来没有头绪,无从下手。

在我看来 react ssr实现起来并不复杂,这个突破口就是从原理出发,一点一点的进行分析,然后通过代码校验你的分析。

react ssr 原理很容易理解,但是仅仅理解这点还不够足以搭建应用骨架,其中最有难度的内容应该是同构。

那么什么是同构?如何实现同构应用呢?

下面请听我慢慢叨叨。我叨叨的同时也会引出问题,希望大家能在看的同时也一起思考起来。

我们上一节说过,传统的ssr和目前流行的csr方案spa都不够完美,所以我们需要能综合这两者优点的技术 - react ssr (SPA+SSR)

每次刷新页面的时候数据是从服务端直出,然后后续的访问就是 spa 的体验,即能解决SEO问题,也能保持页面切换的效率,服务器的压力要比传统的ssr也相对小。

我们现在既然已经知道了该技术的作用和意义,那么接下来就该分析下它的原理了。

为什么react能够实现 SPA+SSR 这种体验呢 ?

虚拟 dom

使用react ssr技术开发出的最终产物其实就是SPA+SSR的结合。

其中的SSR指的是在服务端渲染组件。

而组件可以在服务端渲染的根本原因就是虚拟 DOM

平时我们都习惯使用jsx来编写react 的组件。但jsx只是一个抽象的语法糖,看上去是写组件,其实我们写的是对象,只是这样写更方便,更符合我们前端开发者的编写习惯,看上去就像写html,多爽。

虚拟 DOM 除了在渲染时用于提高渲染性能,以最小的代价来更新视图的作用外,另一个作用就是为组件的跨平台渲染提供可能。

虚拟DOM本身 就是一个内存中的对象,通过对象的属性来描述要渲染的具体是什么元素以及内容。

举个栗子

下面是我们一个组件的render部分

<ul id='list'>
  <li class='item'>1</li>
  <li class='item'>2</li>
  <li class='item'>3</li>
</ul>

上面的结构可以转换为下面的对象表示(虚拟 dom)

const tree = {
  tag:'ul', // 节点标签名
  props: {       // DOM的属性,用一个对象存储键值对
    id: 'list'
  },
  children: [    // 该节点的子节点
    {tag: 'li', props: {class: 'item'}, children: ['1']},
    {tag: 'li', props: {class: 'item'}, children: ['2']},
    {tag: 'li', props: {class: 'item'}, children: ['3']},
  ]
}

从上面我们可以看出这就是个普通对象。

既然有了这样的对象,我们就可以轻松的把这个对象转换我们想要的表现形式,比如 html格式,而这个html就是我们要直出的内容。

不过这个转换的过程不需要我们来完成,react已经帮我们完成,其本身就已提供了内置方法来支持服务端渲染。

同构

React 虚拟 DOM 为我们实现 SSR 提供了基础条件,但是单纯的 SSR 和 传统的 SSR 没有什么区别,React中的 组件也只能用作其他模板语言的代替品。。。

那么为什么还要基于React来实现SSR呢?

既然这种技术能够出现,肯定是因为有他独特的魅力和优点。

我们要明白一点,服务端渲染的核心作用。

SSR主要是直接表达出页面最基础和核心的内容这就够了。

剩下的工作就要交给浏览器了,浏览器端需要对页面的交互完成进一步的渲染、事件绑定等增强功能。

说到这里好像有点明白了,意思不就是服务端把首屏的内容直出,让用户更快的看到页面,然后后面的数据采用js来异步请求和加载。貌似不用react一样可以做到的呀。

诶,好像说的没啥毛病。确实方案不只一种,但是我们基于react来实现可以更高效,写更少的代码。因为我们可以构造同构应用。

所谓同构,就是指前后端公用一套代码,比如我们的组件可以在服务端渲染也可以在客户端渲染,但都是同一个组件。这样的方式应该是可以甩传统方式好几条街了把。

当然打造同构应用还有另外一个得天独厚的条件,双端使用同一种语言 - javascript。

SSR 部分我们使用node就能完成,所以我们才可以编写同一套代码供双端执行。

另外还有一个重要的特性也是同构的重要体现,浏览器接管页面后的进一步渲染(交互、事件)过程中,会判断已有的DOM结构和浏览器渲染出的结构是否相同,若相同,则不重复渲染,只需要绑定事件即可。

当然上面的这个特性是react提供的双端节点对比功能,也是为了最大限度的提高页面的渲染效率,尽可能的重用服务端给出的html结构。

打造同构应用

说了这么多其实本质还是react的能力,有了它的支持才能玩的转,这当然也得力于node

说到这里,可能有同学会这样认为,既然react都为我们提供了,那我们实现起来就很方便了呀。和我们做SPA应用的时候差不多吧,只写一套代码,然后在服务端调用下react服务端渲染的相关 api ,浏览器端也不需要管,react也帮我们搞定了。

说的好像没啥毛病,但是打造同构应该不是仅仅调用几个api的事儿,如果你只是打算写一个demo玩玩,我觉得是可以的。

同构的最大优点是双端可以公用一套代码,但它是一把双刃剑,因为他还涉及到服务端,所以复杂性大大增加。

另外双端也不是完全能公用一套代码,还需要做很多差异化的处理。不只是代码层面的,还会涉及到架构和工程化。

虽然我们已经了解了react ssr的最核心的原理,但是并不能保证你能迅速的开发出这样体验的应用。

所以我们需要一个轮子,这个轮子本身已经帮我们完备了双端的差异处理,开发者只需要关心自身业务逻辑,开发中无差异化。

而这个轮子就是我们接下来要一步一步实现的React SSR应用开发骨架。

双端对比机制

上面也提到了这个概念,这里需要详细的说明一下。

为了实现服务端渲染,打造同构应用,React内部实现了相关的API,可以让我们方便的将一个组件转换为html字符串。

下面介绍几个API

import ReactDOMServer from'react-dom/server'

ReactDOMServer 类可以帮我们在服务端渲染组件 - 得到组件的 html 字符串。

下面是介绍该模块的两个方法

  • renderToString()
ReactDOMServer.renderToString(element)

把一个React组件渲染为原始的HTML

我们可以用这个方法在服务端生成HTML字符串,然后将该字符串返回给浏览器端,完成页面内容的初始化,同时让搜索引擎可以抓取你的页面来达到优化SEO的目的。

另外在react 16前该方法生成的html内容的每一个DOM节点都有一个data-react-id属性,根节点会有一个data-react-checksum属性。

组件在服务端渲染后,在浏览器端还会渲染一次,来完成组件的交互等逻辑。渲染时,react在浏览器端会计算出组件的data-react-checksum属性值,如果发现和服务端计算的值一致,则不会进行客户端渲染。所以data-react-checksum属性的作用是为了完成组件的双端对比。

如果两个组件的propsDOM结构是相同的,那么计算出的该属性值就是一致的。

也可以换个角度来理解,当双端渲染的组件的propsDOM结构一致时,那么该组件只会渲染一次,客户端会采用服务端渲染的结果,仅作事件绑定等处理,这会让我们的应用有一个非常高效的初次加载体验。

ps:data-react-checksum属性值是通过Adler-32校验算法实现的。有兴趣的可以了解下此算法,这里就不详细说明了。

  • renderToStaticMarkup()
ReactDOMServer.renderToStaticMarkup(element)

该方法就比较轻量了,仅仅是为了将组件渲染为html字符串,不会带有data-react-checksum属性。

和上面方法的能力不同,当然使用场景也不同,如果只是单纯服务端渲染的话可以用该方法,性能肯定要比上面的方法高,因为不需要计算嘛,还能减少直出的内容体积。

性能提升

咱们上面说的都是react 16以前的,现在是什么样的呢?

react 16开始,服务端渲染renderToString方法渲染的结果不再有data-react-*属性,当然也相应的提供了一个客户端渲染API - ReactDOM.hydrate(),从使用上来说和ReactDOM.render()没有差别。

在浏览器端渲染时,该方法会最大限度的保留服务端使用renderToString()渲染的内容,同时添加事件绑定等交互。

  • renderToNodeStream 和 renderToStaticNodeStream

另外 react 16 在性能上还做了改进,提供了可以将组件转换为字节流的renderToNodeStream方法。

其实使用renderToNodeStream或者renderToString对最终的渲染结果没有影响。不过renderToNodeStream的性能要好的多,可以有效缩短TTFB时间。

因为组件渲染为字符串,是一次性处理完后才开始向浏览器端返回结果。而采用流的话,可以边读边输出,可以要让页面更快的展现,缩短首屏展现时间。

那么renderToStaticNodeStream可以结合 renderToStaticMarkup理解下,作用应该很明了了。

同构应用流程图

上面我们介绍了很多理论性的知识,可能不够具象。

为了加强理解我准备了一张同构应用的流程图。

小结

本节我们主要从原理来了解下react ssr技术,目的是希望能对该技术有更深的理解和认识。

另外可以根据同构应用流程图来对我们的应用骨架有个宏观的认识,后面我们就要进入实践阶段,一步一步的来打造我们的应用骨架了。

造轮子,所需要的工具

node10.14  其实支持async await的版本就可以
react16.8 
react-router5 
redux
redux-thunk
webpack4 
babel7 
koa2 ,小册里使用 koa2,当然用 express 也可以
...其他的一些插件和库,细节在后面说明

let's go!一起去造轮子啦!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zz_jesse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值