美文网首页
@loadable/components是如何实现懒加载的

@loadable/components是如何实现懒加载的

作者: 相遇一头猪 | 来源:发表于2022-04-23 15:58 被阅读0次

    先看下官方文档是如何使用@loadable/component

    import loadable from '@loadable/component'
    const OtherComponent = loadable(() => import('./OtherComponent'))
    function MyComponent() {
      return (
        <div>
          <OtherComponent />
        </div>
      )
    }
    

    loadable(() => import('./OtherComponent')) 的返回值是一个React组件。那究竟是如何做到懒加载的?

    本文不会讲解import('./OtherComponent')如何分包,如果想了解请自行查阅资料。

    createLoadable

    源码中loadablecreateLoadable生成。createLoadble接受defaultResolveComponentrender两个参数,执行后返回loadable

    import React from 'react'
    import createLoadable from './createLoadable'
    import { defaultResolveComponent } from './resolvers'
    
    export const { loadable, lazy } = createLoadable({
      defaultResolveComponent,
      render({ result: Component, props }) {
        return <Component {...props} />
      },
    })
    

    至于defaultResolveComponent这里先不用管,下文使用到会介绍。

    接下来就是最核心的createLoadable ,它的代码框架如下:

    image.png

    createLoadable内部定义并返回了loadablelazy两个函数。其中我们的关注重点是官方文档提到的loadable

    loadable

    loadable有2个参数:

    1. 构造函数,
    2. options选项。

    首先,loadable会先执行resolveConstructor,对构造函数包装:

    const ctor = resolveConstructor(loadableConstructor); 
    

    包装后,ctor的值如下:

    const ctor = {
        requireAsync: () => import('./OtherComponent'),
        resolve() {
            return undefined
        },
        chunkName() {
            return undefined
        }
    }
    

    编译后的ctor值:


    loadable:component.png

    InnerLoadable

    loadable内部还定义了一个类组件InnerLoadable

      class InnerLoadable extends React.Component {
          constructor(props) {
            super(props)
    
            this.state = {
              result: null,
              error: null,
              loading: true,
              cacheKey: getCacheKey(props),
            }
          }  
          // ........  
       }
    
        const EnhancedInnerLoadable = withChunkExtractor(InnerLoadable)
        const Loadable = React.forwardRef((props, ref) => (
          <EnhancedInnerLoadable forwardedRef={ref} {...props} />
        ))
        
        Loadable.displayName = 'Loadable';
        return Loadable;
    

    其中withChunkExtractor是一个高阶组件,InnerLoadable经过HOC withChunkExtractor包装后再由React.forwardRef包装一次。
    因此,Loadable实质上也是一个高阶组件,至于具体是如何加载bundle的请继续往下阅读。

    组件加载阶段请求bundle

    在生命周期componentDidMount执行loadAsync

        componentDidMount() {
           this.mounted = true
        
           // .....
           
           // component might be resolved synchronously in the constructor
           if (this.state.loading) {
                this.loadAsync()
            }
          }
     
    

    loadAsync中去加载bundle(实际上源码内部调用了多个方法,为了阅读方便,我把方法拆开简化后都写到loadAsync中),加载成功后bundle存到result,同时loading变为false。

      loadAsync() {
          const { __chunkExtractor, forwardedRef, ...props } = this.props;
            
            /**
            *  这一步会请求需要懒加载的包, 对应代码import('./OtherComponent')
            */
           let promise = ctor.requireAsync(props);  
            
            promise
              .then(loadedModule => {
                const result = resolve(loadedModule, this.props);
                if(this.mounted) {
                   this.setState({
                     result,
                     loading: false,
                   })
                }            
              })
              .catch(error => this.safeSetState({ error, loading: false }))
    
            return promise
       }
    

    resolve中判断使用者是否传入resolveComponent,如果有就用resolveComponent去处理module和props,如果没有则使用defaultResolveComponent返回module。

       function resolve(module, props) {
          const Component = options.resolveComponent
            ? options.resolveComponent(module, props)
            : defaultResolveComponent(module)
    
          return Component
       }
    

    defaultResolveComponent也是一个函数,传入一个module作为参数,如果是esModule则返回module.default ,否则返回 module.default || module:

    export function defaultResolveComponent(loadedModule) {
      return loadedModule.__esModule
        ? loadedModule.default
        : loadedModule.default || loadedModule
    }
    

    componentDidMount加载bundle后,loading变为false,在render中执行createLoadable的参数render后返回:

        render() {
            const {
              forwardedRef,
              fallback: propFallback,
              __chunkExtractor,
              ...props
            } = this.props
            const { loading, result } = this.state
        
            const fallback = propFallback || options.fallback || null
    
            if (loading) {
              return fallback
            }
    
            return render({
              fallback,
              result,
              options,
              props: { ...props, ref: forwardedRef },
            })
          }
    

    代码中执行的render如下:


    render.png

    总结

    loadable是一个高阶组件,在componentDidMount加载 bundle (() => import('./xxx')被打包后的bundle),如果没有加载或者加载未完成则在render中渲染fallback,加载完成则渲染组件。

    相关文章

      网友评论

          本文标题:@loadable/components是如何实现懒加载的

          本文链接:https://www.haomeiwen.com/subject/yayaertx.html