伴随着React协议的『妥协』(v16采用MIT),React为项目的主体,这个在短期内是不会改变的了,在平时使用过程中发现了如下这个问题:

在服务器渲染的时候,刷新页面会出现闪屏的现象(白屏一闪而过)

作为努力最求极致的我,是不能容忍的,而这一现象是半道出现的,也就是在添加按需加载之后。要说清楚这个问题,得从React的服务器渲染开始说起,(急于寻求问题解决方案的,可以直接去文章后半部分)

服务器渲染(SSR)基础原理

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

基本设计理念

React 提供了两个可用于服务端渲染组件的函数:ReactDOMServer.renderToStringReactDOMServer.renderToStaticMarkup。 在设计用于服务端渲染的ReactComponent时需要有预见性,考虑以下方面:

  • 选取最优的渲染函数。

  • 如何支持组件的异步状态。

  • 如何将应用的初始化状态传递到客户端。

  • 哪些生命周期函数可以用于服务端的渲染。

  • 如何为应用提供同构路由支持。

  • 单例、实例以及上下文的用法。

渲染函数

render()

我们常见的render方法,用于浏览器渲染。

import ReactDOM from 'react-dom';
ReactDOM.render(
element,
container,
[callback]
)

renderToString()

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

  • 用法:

const ReactDOMServer = require('react-dom/server');
ReactDOMServer.renderToString(element)
  • 例子:

const ReactDOMServer = require('react-dom/server');
const Hello = React.createClass({
render: function() {
  return <div>hello</div>;
}
});
const helloString = ReactDOMServer.renderToString(
React.createElement(Hello)
);
/*
输出结果大概为:
helloString = `
<div
data-reactid=".xxx"
data-react-checksum="-123456"
>
hello
</div>
`
*/

从上面这个例子,很容易发现,React为div注入了一些自定义属性,首先reactid,这是在浏览器环境下,React为了区分DOM节点,在需要更新的时候能够精确定位的标记。而后checksum这个属性仅仅存在与服务端,这个你可能没见过,或没留意,它的作用是拿服务端返回的String与已创建的DOM做校验,这就准许了React在客户端和服务端在结构上拥有相同的DOM结构,该属性只会添加在根节点元素上。拿到checksum大抵会做一些事情:

  • 检查第一个元素是否有data-react-checksum属性,如果有则通过ReactDOMServer.renderToString拿到前端的,通过adler32算法得到的值和data-react-checksum对比,如果一致则表示,无需渲染,否则重新渲染。

renderToStaticMarkup()

import ReactDOMServer from 'react-dom/server';
ReactDOMServer.renderToStaticMarkup(element)
// eg:
ReactDOMServer.renderToStaticMarkup(
React.createElement(
  Provider,
  { store },
  React.createElement(
    RouterContext,
    matchedData[1]
  )
)
);

这个函数和上面的那个函数大体相同,除却,它不会给节点添加任何额外的属性值,它的返回值是『干净』的,在某种情况下,可以节约空间使用。

选择

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

  • 生成HTML电子邮件

  • 通过HTML到PDF的转化来生成PDF

  • 组件测试

  • 等一些需要『纯』DOM的情况下

大多数情况下,我们都会选择使用ReactDOMServer.renderToString。这将准许React使用data-react-checksum在客户端迅速的初始化同一个React Component,因为React可以重用服务端提供的DOM,所以它可以跳过生成DOM节点以及把他们挂载到文档中这两个昂贵的进程,对于复杂些的站点,这样做就会显著的减少加载时间,用户可以更快的与站点进行交互。确保React Component能够在服务端和客户端准确的渲染出一致的结构是很重要的。如果data-react-checksum不匹配,React会舍弃服务端提供的DOM,然后生成新的DOM节点,并且将它们更新到文档中。此时,也就不具备服务端渲染带来的各种性能上的优势。这个错误会是下面这样的,如果你开了React dev模式:

Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
(client) <noscript data-reacti
(server) <div data-reactid=".q

而解决这个问题,则是保证在服务器环境下能够渲染出与客户端一样的DOM结构,总结一下一般会是两种情况:

  • 异步/延迟加载

  • 存在随机逻辑

  • 最大的问题是,客户端和服务端的环境差异造就的问题,如document环境下的一些元素无法在服务端渲染等

总结

上面讲了些服务器渲染需要注意到的点,而一开始提到的按需加载刷新页面出现的闪屏问题还没有落实。以下:在异步加载的时候服务器渲染和客户端渲染的结果不同,即通过checksum校验失败,重新渲染,在这个重新渲染的时候,需要重新匹配路由,以上就会出现闪屏的情况。我们注意到,在使用react-router的时候,服务器中使用到了一个match函数

match({ history, routes: getRoutes(store), location: req.originalUrl }, (error, redirectLocation, renderProps) => {})

简单的说他匹配分析了客户端的路由,使得渲染过程中能够吻合到客户端的路由控制器。而如果客户端在首次出现需要重新渲染的时候,如果是动态路由(按需加载使用到的一项技术),就需要重新匹配渲染,这时候会出现短暂的白屏闪过。解决这个问题,只需要在客户端渲染之前先匹配路由,使用match。(关于match的详细介绍,参见官方文档)

// +redux
// 客户端在渲染前加上匹配路由函数match
match({ history, routes }, (error, redirectLocation, renderProps) => {
if (!error) {
  // 渲染
  ReactDOM.render(
    <Provider store={store}>{routes}</Provider>,
    document.getElementById('root')
  );
} else {
  console.error(error);
  // todo: 错误信息收集
}
});

以上,便可解决题中问题。

React Router 按需加载+服务器渲染的闪屏问题的更多相关文章

  1. vue router按需加载

    import Vue from 'vue' import Router from 'vue-router' Vue.use(Router); //按需加载,当渲染其他页面时才加载其组件,并缓存,减少首 ...

  2. 深入浅出的webpack4构建工具---webpack+vue+router 按需加载页面(十五)

    1. 为什么需要按需加载? 对于vue单页应用来讲,我们常见的做法把页面上所有的代码都打包到一个bundle.js文件内,但是随着项目越来越大,文件越来越多的情况下,那么bundle.js文件也会越来 ...

  3. react路由按需加载方法

    使用router4之后以前的按需加载方法require.ensure 是不好使了. 所以我们改用react-loadable插件做按需加载. 第一步: yarn add react-loadable ...

  4. 配置react / antd 按需加载 并且使用less(react v16)

    1.开启项目   并且执行 yarn eject 下载好我们需要的插件(babel-plugin-import   less  less-loader   antd  react-loadable   ...

  5. easyUI datagrid 刷新取消加载信息 自动刷新闪屏问题

    <style type="text/css"> /*-- 消除grid屏闪问题 --//*/ .datagrid-mask { opacity: 0; filter: ...

  6. React Router 4.0 + webpack 实现组件按需加载

    网上关于React Router 4.0的按需加载文章有很多,大致的思路都一样,但是其实具体实现起来却要根据自己的实际情况来定,这里主要介绍一下我的实现方式. 主要方式是通过Route组件的rende ...

  7. EasyDSS高性能流媒体服务器前端重构(五)- webpack + vue-router 开发单页面前端实现按需加载 - 副本

    为了让页面更快完成加载, 第一时间呈现给客户端, 也为了帮助客户端节省流量资源, 我们可以开启 vue-router 提供的按需加载功能, 让客户端打开页面时, 只自动加载必要的资源文件, 当客户端操 ...

  8. EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV、RTSP流媒体服务器前端重构(五)- webpack + vue-router 开发单页面前端实现按需加载

    为了让页面更快完成加载, 第一时间呈现给客户端, 也为了帮助客户端节省流量资源, 我们可以开启 vue-router 提供的按需加载功能, 让客户端打开页面时, 只自动加载必要的资源文件, 当客户端操 ...

  9. react按需加载(getComponent优美写法),并指定输出模块名称解决缓存(getComponent与chunkFilename)

    react配合webpack进行按需加载的方法很简单,Route的component改为getComponent,组件用require.ensure的方式获取,并在webpack中配置chunkFil ...

随机推荐

  1. redis数据库操作的C++简单封装

    用c++简单封装了redis的基本操作(hiredis) 接口包括:①链接和断开连接.②设置键值对(set).③查询键值对(get).④删除键值对(del).⑤将所有键显示出来 若任何一处发生错误,返 ...

  2. 创建springbootdemo后运行报MongoSocketOpenException错误解决方法

    在类SpringbootdemoApplication上右键Run as选择Spring Boot App后Console输出报错日志如下: com.mongodb.MongoSocketOpenEx ...

  3. 关于样式选择器:hover出现忽闪现象

    有时候在做项目的时候会想给一个元素添加一个遮罩效果,然后鼠标划过去的时候,遮罩消失,一般最简单的方法就是使用样式选择器:hover,但是经历过才知道恶心,有种灯泡快要坏的感觉,闪瞎自己的眼睛,于是诞生 ...

  4. java初步—参数的值传递

    校招季,本人匆匆忙忙地参加各种宣讲会,几次笔试下来都遇到同一个题目,而且全都错在同一想法上,方知自己的基础实在不太牢固,因此特别写在博客上提醒自己要脚踏实地地学习!不多说了,题目如下: public ...

  5. ADO.NET中SQL Server数据库连接池

    连接到数据库服务器通常由几个需要很长时间的步骤组成. 必须建立物理通道(例如套接字或命名管道),必须与服务器进行初次握手,必须分析连接字符串信息,必须由服务器对连接进行身份验证,必须运行检查以便在当前 ...

  6. jsp、freemarker、velocity区别详解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt172 在java领域,表现层技术主要有三种:jsp.freemarker.v ...

  7. [js高手之路]Vue2.0基于vue-cli+webpack Vuex用法详解

    在这之前,我已经分享过组件与组件的通信机制以及父子组件之间的通信机制,而我们的vuex就是为了解决组件通信问题的 vuex是什么东东呢? 组件通信的本质其实就是在组件之间传递数据或组件的状态(这里将数 ...

  8. Window下SVN服务器搭建以及客户端使用

    一.下载 上一篇博客是关于Jenkins的内容,在Jenkins自动化编译时可能会自动获取版本更新进行build,那就需要用到版本更新的工具.这里使用VisualSVN Server来作为搭建svn的 ...

  9. JavaSE(十)集合之Set

    今天这一篇把之前没有搞懂的TreeSet中的比较搞得非常的清楚,也懂得了它的底层实现.希望博友提意见! 一.Set接口 1.1.Set集合概述 Set集合:它类似于一个罐子,程序可以依次把多个对象 “ ...

  10. IT之光

    作为一个IT界的新新人才,现在拥有第一个博客,可以在这里学习和分享IT方面的知识和技术.