本文转载自微信公众号「前端Sharing」,进阶漫今生作者前端Sharing。异步转载本文请联系前端Sharing公众号。组件 今天我们聊一聊React中的前世异步组件的现况和未来,异步组件很可能是进阶漫今生未来从数据交互到UI展示一种流畅的技术方案,所以既然要吃透React,异步进阶React,组件就有必要搞懂异步组件。前世 老规矩,进阶漫今生我们还是异步带着问题开始今天的思考?(自测掌握程度) 我们先来想想目前的React应用中使用ajax或者fetch进行数据交互场景,基本上就是组件这样的,在类组件中componentDidMount和函数组件effect中进行数据交互,得到数据后,再渲染UI视图。那么可不可以让组件的渲染等待异步数据请求完毕,得到数据后再进行render呢? 对于上面这种情况,云南idc服务商第一感觉是难以置信,如果能够实现让渲染中断,等到数据请求之后,再渲染呢?那就是Susponse,上面说到的不可能实现的事,Susponse做到了,React 16.6 新增了,Susponse 让组件“等待”某个异步操作,直到该异步操作结束即可渲染。 传统模式:渲染组件-> 请求数据 -> 再渲染组件。 异步模式:请求数据-> 渲染组件。 一个传统模式下的数据交互应该是这个样子的。 流程:页面初始化挂载,useEffect里面请求数据,通过useState改变数据,二次更新组件渲染数据。 那么如果用Susponse异步模式就可以这么写: 当数据还没有加载完成时候,会展示Suspense中 fallback的内容,弥补请求数据中过渡效果 ,尽管这个模式在现在版本中还不能正式使用,但是将来 React 会支持这样的代码形式。 至于Suspense是源码库如何将上述不可能的事情变成可能的呢?这就要从 componentDidCatch 说起了,在 React 推出 v16 的时候,就增加了一个新生命周期函数 componentDidCatch。如果某个组件定义了 componentDidCatch,那么这个组件中所有的子组件在渲染过程中抛出异常时,这个 componentDidCatch 函数就会被调用。 componentDidCatch使用 componentDidCatch 可以捕获异常,它接受两个参数: 1 error —— 抛出的错误。 2 info —— 带有 componentStack key 的对象,其中包含有关组件引发错误的栈信息。 我们来模拟一个子组件渲染失败的情况: 如上,我们模拟一个render失败的场景,将一个非React组件Children1当作正常的React的组件来渲染,这样在渲染阶段就会报错,错误信息就会被 componentDidCatch捕获到,错误信息如下: 对于如上如果在渲染子组件的时候出现错误,会导致整个组件渲染失败,无法显示,正常的云服务器组件Children也会被牵连,这个时候我们需要在componentDidCatch做一些补救措施,比如我们发现 componentDidCatch 失败,可以给Children1加一个状态控制,如果渲染失败,那么终止Children1的render。 如果出现错误,通过setState重新渲染,并移除失败的组件,这样组件就能正常渲染了,同样也不影响Children挂载。componentDidCatch一方面捕获在渲染阶段出现的错误,另一方面可以在生命周期的内部执行副作用去挽回渲染异常带来的损失。 componentDidCatch原理 componentDidCatch原理应该很好理解,内部可以通过try{ }catch(error){ }来捕获渲染错误,处理渲染错误。 componentDidCatch思想能否迁移到Suspense上 那么回到我们的异步组件上来,如果让异步的代码放在同步执行,是肯定不会正常的渲染的,我们还是要先请求数据,等到数据返回,再用返回的数据进行渲染,那么重点在于这个等字,如何让同步的渲染停止下来,去等异步的数据请求呢?抛出异常可以吗? 异常可以让代码停止执行,当然也可以让渲染中止。 Suspense 就是用抛出异常的方式中止的渲染,Suspense 需要一个 createFetcher 函数会封装异步操作,当尝试从 createFetcher 返回的结果读取数据时,有两种可能:一种是数据已经就绪,那就直接返回结果;还有一种可能是异步操作还没有结束,数据没有就绪,这时候 createFetcher 会抛出一个“异常”。 这个“异常”是正常的代码错误吗?非也,这个异常是封装请求数据的Promise对象,里面是真正的数据请求方法,既然 Suspense 能够抛出异常,就能够通过类似 componentDidCatch的try{ }catch{ }去获取这个异常。 获取这个异常之后干什么呢? 我们知道这个异常是Promise,那么接下来当然是执行这个Promise,在成功状态后,获取数据,然后再次渲染组件,此时的渲染就已经读取到正常的数据,那么可以正常的渲染了。接下来我们模拟一下createFetcher和Suspense 我们模拟一个简单createFetcher 我们模拟一个简单的Suspense 用 componentDidCatch 捕获异步请求,如果有异步请求渲染 fallback,等到异步请求执行完毕,渲染真实组件,借此整个异步流程完毕。但为了让大家明白流程,只是一次模拟异步的过程,实际流程要比这个复杂的多。 流程图: Suspense带来的异步组件的革命还没有一个实质性的成果,目前版本没有正式投入使用,但是React.lazy是目前版本Suspense的最佳实践。我们都知道React.lazy配合Suspense可以实现懒加载,按需加载,这样很利于代码分割,不会让初始化的时候加载大量的文件,减少首屏时间。 React.lazy接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise ,该 Promise需要 resolve 一个 default export 的 React 组件。 我们先来看一下基本使用: 我们用Promise模拟一下 import()效果,将如上 LazyComponent改成如下的样子: 效果: React.lazy 是如何配合Susponse 实现动态加载的效果的呢?实际上,lazy内部就是做了一个createFetcher,而上面讲到createFetcher得到渲染的数据,而lazy里面自带的createFetcher异步请求的是组件。lazy内部模拟一个promiseA规范场景。我们完全可以理解React.lazy用Promise模拟了一个请求数据的过程,但是请求的结果不是数据,而是一个动态的组件。 接下来我们看一下lazy是如何处理的 React.lazy包裹的组件会标记REACT_LAZY_TYPE类型的element,在调和阶段会变成 LazyComponent 类型的fiber,React对LazyComponent会有单独的处理逻辑,第一次渲染首先会执行 _init 方法。此时这个_init方法就可以理解成createFetcher。 我们看一下lazy中init函数的执行: react-reconciler/src/ReactFiberBeginWork.js 流程图 你当下并不使用 Relay,那么你暂时无法在应用中试用 Suspense。因为迄今为止,在实现了 Suspense 的库中,Relay 是我们唯一在生产环境测试过,且对它的运作有把握的一个库。 目前Suspense还并不能,如果你想使用,可以尝试一下在生产环境使用集成了 Suspense 的 Relay。Relay 指南! Suspense能解决什么? Suspense面临挑战? 对于未来的Suspense能否作为主流异步请求数据渲染的方案,笔者认为Suspense未来还是充满期待,那么对于Suspense的挑战,个人感觉在于以下几个方面: 1 concurrent模式下的Susponse可以带来更好的用户体验,react团队能够让未来的Suspense更灵活,有一套更清晰明确的createFetcher制作手册,是未来的concurrent模式下Suspense脱颖而出的关键。 2 Suspense能否广泛使用,更在于 Suspense 的生态发展,有一个稳定的数据请求库与Suspense完美契合。 3 开发者对Suspense的价值的认可,如果Suspense在未来的表现力更出色的话,会有更多开发者宁愿自己封装一套数据请求方法,给优秀的Suspense买单。 本文讲了React Susponse的由来,实现原理,目前阶段状态,以及未来的展望,对于React前世与今生,你有什么看法呢?一 前言
二 初识:异步组件
1 什么是异步组件
2 开启Suspense模式
三 溯源:从componentDidCatch到Suspense
四 实践:从Suspense到React.lazy
React.lazy简介
React.lazy基本使用
const LazyComponent = React.lazy(()=>import(./text)) React.lazy原理解读
五 展望:Suspense未来可期
六 总结