CSR和SSR
CSR(客户端渲染,也就单页面渲染SPA):
1.浏览器请求空的html,html中引入JS。
2.加载JS,从服务器请求初始数据,生成虚拟DOM,然后渲染成最终页面。
3.不同路由页面,引入不同js,修改DOM视图。
SSR(服务端渲染,一般有中间层Node服务器):
1.node端会打包两份js,一份是服务端js,一份是客户端js。
2.浏览器的页面请求到达Node,Node通过路由找到对应的页面组件(服务端js),加载js,从后台服务器获取组件初始数据。通过react-dom/server,生成流式HTML(字符串),此html里没有事件绑定(个人理解服务端通过js生成的html没办法绑定事件)。同时把初始数据也传给浏览器(注水),通过js把数据放到windows的全局变量里。
3.浏览器拿到有内容的html后直接渲染,同时下载客户端js。加载js,初始数据就不再请求了,直接从全局对象中拿(脱水),生成虚拟DOM。新旧节点对比,发现完全一样,不会重新渲染组件,但此过程为dom节点绑定了事件。
4.完整的页面渲染完成,路由请求新的页面时,改变url,从新执行1-4步。
SSR的一些说明
SSR 之所以能够实现,有两个重要前提,缺一不可:
1. Node.js的存在
在浏览器中的 JS 的用途是操作 DOM,浏览器就提供了 document 之类的内置对象。而运行在 NodeJS 中的 JS 的用途是操作磁盘文件或搭建 HTTP 服务器,NodeJS 就相应提供了 fs、http 等内置对象。
2. 虚拟 DOM 的存在
前面讲到,js操作 DOM 的能力是浏览器给予它的, 而在 Node 环境下,是没有 DOM 这个概念存在的,如果你的 React 代码里存在直接操作 DOM 的代码,在 Node 环境下这些代码会报错,就无法实现同构了。幸好在 React 框架中引入了虚拟 DOM 这个概念
服务器端已经渲染了一次 React 组件,如果在客户端中再渲染一次 React 组件,会不会渲染两次 React 组件?
服务端使用 renderToString 渲染组件,生成的是html字符串(给客户端前还要加入script标签,好在客户端再拉与一下js)。生成的HTML DOM 是带有data-react-checksum属性的,如果两个组件有相同的 props 和 DOM 结构,会算出相同的data-react-checksum,所以在客户端进行渲染时,会先计算出组件的 checksum 值,然后检查 HTML DOM 是否存在相同的 data-react-checksum,如果一致,则客户端可以直接使用服务端生成的 DOM 树,不会重复渲染。如果不一致,则客户端会重新渲染不同的HTML部分。这样就使得服务端渲染的视图和客户端渲染的视图保持一致。
同构数据获取
在平常客户端的React开发中,我们一般在组件的 componentDidMount 生命周期函数进行异步数据的获取。然而服务器端始终不会执行 componentDidMount ,这样就导致服务端拿不到数据。现在我们的工作就是让服务端将获得数据的操作执行一遍,以达到真正的服务端渲染的效果。
1.react挂载时相关的四个生命周期(执行顺序由上而下):
constructor()
componentWillMount()
render()
componentDidMount()
首先,获取异步数据为什么放在componentDidMount中:
- 数据获取可以放在
constructor或者componentDidmount中,不建议放在componentWillMount。 但是为了更好的代码规范和可读性,建议统一放在componentDidmount,毕竟是react官方推荐。
2.React16引入了React Fiber的概念,导致了componentWillMount,componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate这些生命周期出现了可能被调用不止一次的可能,这应该也是这些生命周期将会被废弃的原因。
3.在ssr中,componentWillMount是服务端渲染唯一会调用的生命周期函数。如果在 componentWillMount 里获取数据,fetch data会执行两次,一次在服务端一次在客户端,使用 componentDidMount 则没有这个问题。
这里和vue不同,vue获取异步数据,通常会放在created中,个人认为,首先vue中无论何时data中的数据被更新,都会重新渲染,所以请求时机越早越好。react则不同,它是通过setState来重新渲染的,它的设计理念就是在 constructor 中做一些状态初始化工作,你非要在里面做异步请求,或多或少可能带来意想不到的问题,而且从速度而言,render速度要远快于网络请求,这点效率可以忽略不记,想想vue中,很多请求放mounted中,是不是也没什么问题。
2.既然放在componentDidMount中,服务端怎么获取初始数据呢
服务器端渲染时,我们通过添加一个loadData 函数来获取的数据。然后通过注水,脱水把数据给客户端,避免客户端再次请求数据(时间不同两次请求的数据还会不一样呢)。