什么是Suspense
我们来看下,React官方站点是如何解释的。
Suspense lets components “wait” for something before rendering. Today, Suspense only supports one use case: loading components dynamically withReact.lazy. In the future, it will support other use cases like data fetching.
具体链接
大致的意思是,Suspense可以让组件以类似等待的状态,等待某些行为结束后再进行渲染。当前,Suspense仅支持React.lazy动态加载组件的场景。在将来会支持其他类似数据获取等场景。
// This component is loaded dynamicallyconst OtherComponent = React.lazy(() => import('./OtherComponent'));function MyComponent() { return ( // Displays <Spinner> until OtherComponent loads <React.Suspense fallback={<Spinner />}> <div> <OtherComponent /> </div> </React.Suspense> );}
React.Suspense相对来说,它在React2018年的16.6.0版本中发布,大家肯定从很多地方听说过,除了官方的演示demo,相信很少在实际代码中去使用,今天笔者带着大家从实际应用中介绍下如何去使用。
具体场景
我们经常在React项目中会去使用一些比较大型的组件,比如加载地图,Echarts图表或者富文本编辑器等,大家可以跟着笔者一步步实践下,React.Suspense在类似的场景下,如何优化体验。
这里重点讲下,本文想描述的是动态加载重量级组件的场景,类似想tinymce或者其他库,目前都提供了对应的React实现的npm包,比如@tinymce/tinymce-react,然而在实际使用过程中,往往并不是用户一进页面就会使用这种组件,所以从首屏渲染的性能的角度,我们会使用组件懒加载等方式,因此存在异步加载时的等待情况,导致组件闪烁等情况。从工程化优化的角度,尤其是微前端场景下,这种大型的组件,在多项目中同时使用的话,会存在重复打包的情况,建议排除在打包范围之外,使用umd的方式(利用浏览器本身的缓存)或者单独封装打包处理。
我们还是用umi快速搭建个简单的环境
mkdir myapp && cd myappnpx @umijs/create-umi-app
然后我们引入tinymce这个富文本编辑器
npm install --save @tinymce/tinymce-react
然后我们在src/pages目录下创建个文件editor.tsx,笔者从tinymce的官方React示例中复制一份代码,链接
import React, { useRef } from 'react';import { Editor } from '@tinymce/tinymce-react';export default function App() { const editorRef = useRef(null); const log = () => { if (editorRef.current) { console.log(editorRef.current.getContent()); } }; return ( <> <Editor onInit={(evt, editor) => editorRef.current = editor} initialValue="<p>This is the initial content of the editor.</p>" init={{ height: 500, menubar: false, plugins: [ 'advlist autolink lists link image charmap print preview anchor', 'searchreplace visualblocks code fullscreen', 'insertdatetime media table paste code help wordcount' ], toolbar: 'undo redo | formatselect | ' + 'bold italic backcolor | alignleft aligncenter ' + 'alignright alignjustify | bullist numlist outdent indent | ' + 'removeformat | help', content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }' }} /> <button onClick={log}>Log editor content</button> </> );}
然后在.umirc.ts中添加路由配置参考,并启用按需加载dynamicImport,并禁用下umi本身的loading效果
dynamicImport: { loading: '@/components/loading', },routes: [ { exact: true, path: '/', component: 'index' }, { exact: true, path: '/editor', component: 'editor' }, ],
在src/components下新建loading.tsx
import React from 'react';export default () => <></>;
然后我们在index.tsx中添加个跳转按钮
import { Link } from 'umi';import styles from './index.less';export default function IndexPage() { return ( <div> <h1 className={styles.title}>Hello,喵爸的小作坊</h1> <Link to="/editor">去找富文本编辑器</Link> </div> );}
然后我们执行下npm start然后看下效果
在切换路由后,发现了白屏,然后再加载出富文本编辑器,如果我们想要改造下,让用户有所感知,界面正在加载给一个直接反馈,这个事情,我们可以使用React.Suspense新建LoadEditor.tsx,并改造下Editor.tsx,将/editor路由指定到LoadEditor.tsx
import React, {Suspense, useEffect} from 'react';const EditorComponent = React.lazy(() => import('./Editor'));export default function LoadEditor() { return ( <div> <Suspense fallback={<div style={{height:528,width:'100%',display:'flex',alignItems:'center',justifyContent:'center',fontSize:28}}>编辑器加载中!!</div>}> <EditorComponent/> </Suspense> </div> );}
然后我们再看下效果,
我们惊奇的发现,好像并没有什么用,我们自己写的loading框,一瞬间就没了,页面还是白屏中在加载富文本编辑器引用的外部资源,然后过了一段时间才显示出来,为什么是这样?
其实了解相对底层的同学已经知道了,loading效果出来的时候,组件的确是在加载过程中,组件本身的懒加载速度很快,但是并不代表它依赖的外部资源都已经加载完成,一通操作猛如虎,一看战绩0-5的感觉。
在实际场景中,组件本身的大小往往在可接受范围内,更多的是用来处理数据接口的延迟,比如这段来自社区的例子传送门,在此类情况下能够解决不少问题,不需要在组件内部去申明个变量标志当前数据是否加载完成,然后根据接口返回的状态进行切换了。
import createResource from "./magical-cache-provider";const dataResource = createResource(id => fetchData(id));class DynamicData extends Component { render() { const data = dataResource.read(this.props.id); return <p>Data loaded ?</p>; }}class App extends Component { render() { return ( <Suspense fallback={<p>Loading...</p>}> <DeepNesting> <DynamicData /> </DeepNesting> </Suspense> ); }}
如何改造
回到我们上面的例子,就是组件中存在其他资源的加载场景,到底怎么解决呢?
这里先卖个关子,期待下回分解
更多React.Suspense和React.lazy原理性的分析和理解,大家可以参照React官方网站和其他大佬在社区中的文章,笔者也会持续更新。
更多内容可以关注公众号 - 喵爸的小作坊
版权声明:内容来源于互联网和用户投稿 如有侵权请联系删除