React Suspense尝鲜(前奏)

发布一下 0 0

什么是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尝鲜(前奏)

在切换路由后,发现了白屏,然后再加载出富文本编辑器,如果我们想要改造下,让用户有所感知,界面正在加载给一个直接反馈,这个事情,我们可以使用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>  );}

然后我们再看下效果,

React Suspense尝鲜(前奏)


我们惊奇的发现,好像并没有什么用,我们自己写的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官方网站和其他大佬在社区中的文章,笔者也会持续更新。




更多内容可以关注公众号 - 喵爸的小作坊

React Suspense尝鲜(前奏)

版权声明:内容来源于互联网和用户投稿 如有侵权请联系删除

本文地址:http://0561fc.cn/68636.html