React-loadable source code analysis

In short, loadable is a high-order function, which also uses react's rendering API, webpack knowledge points, babel, and promise combined components

use

First of all, we need to know what is the usage of react-loadable

  1. loader
    For components that need to be lazily loaded, the () => import('xxx') syntax must be used to use
  2. loading
    loading component, props accept error, pastDelay, timedOut, retry parameters, can be customized
  3. delay
    Can add delay
  4. timeout
    overtime time
  5. render
    The type is: (loaded, props)=>ReactNode, additional parameter injection can be added

Load multiple components at the same time

The accepted parameters loader, render, and type are not much different from the above

Loadable.Map({
  loader: {
    Bar: () => import('./Bar'),
    i18n: () => fetch('./i18n/bar.json').then(res => res.json()),
  },
  render(loaded, props) {
    let Bar = loaded.Bar.default;
    let i18n = loaded.i18n;
    return <Bar {...props} i18n={i18n}/>;
  },
})

Preloading

const LoadableBar = Loadable({
  loader: () => import('./Bar'),
  loading: Loading,
});


触发:
LoadableBar.preload();

Curry also involves SSR related knowledge points, here is not involved

Source code

Because I don’t talk about SSR related here, I deleted the relevant code here: loadable.jsx

main body

In this high-level component, his main body is: createLoadableComponent

First, let's look at what he did in the closure:

function createLoadableComponent(loadFn, options) {
    // loading 的判断, 忽略

    // 创建配置项, 覆盖默认值
    // 其中 render 源码:  function render(loaded, props) {
    //     return React.createElement(resolve(loaded), props);
    // }
    let opts = Object.assign(
        {
            loader: null,
            loading: null,
            delay: 200,
            timeout: null,
            render: render,
            webpack: null,
            modules: null
        },
        options
    );
    
    // 结果, 用于 调用 loader
    let res = null;
    
    // 初始化时调用, loadFn 函数后面再讲
    function init() {
        if (!res) {
            res = loadFn(opts.loader);
        }
        return res.promise;
    }
    
    return class LoadableComponent extends React.Component{
        // 这里先忽略
    }
}

Return component

Let's look at the returned components again:

class LoadableComponent extends React.Component {
        constructor(props) {
            super(props);
            init(); // 在构造函数中启用初始化函数, 他将 res 赋值为一个 promise
            
            // 定义的 state
            this.state = {
                error: res.error,
                pastDelay: false,
                timedOut: false,
                loading: res.loading,
                loaded: res.loaded
            };
        }
        
        // 静态函数, 之前介绍用法的时候说过了
        static preload() {
            return init();
        }

        componentWillMount() {
            // 用来设置定时器和 delay 相关
            this._loadModule();
        }

        componentDidMount() {
            // 标记是否mounted
            this._mounted = true;
        }
        
        componentWillUnmount() {
            // 修改标记, 清除定时器
            this._mounted = false;
            this._clearTimeouts();
        }

        render() {
            // 渲染函数, 如果当前是 加载中或者错误加载的状态 , 则使用 loading 渲染, 并且传递多种参数
            if (this.state.loading || this.state.error) {
                return React.createElement(opts.loading, {
                    isLoading: this.state.loading,
                    pastDelay: this.state.pastDelay,
                    timedOut: this.state.timedOut,
                    error: this.state.error,
                    retry: this.retry
                });
            } else if (this.state.loaded) {
                // 如果已经加载完毕, 则调用 render 函数, 使用 React.createElement 渲染
                return opts.render(this.state.loaded, this.props);
            } else {
                return null;
            }
        }
    }

load

// 这里的 load 就是 createLoadableComponent(loadFn, options) 中的入参loadFn
function load(loader) {
    // loader 是 options 中的 loader
    // 比如: () => import('./my-component')
    let promise = loader();
    
    // 用来返回结果
    let state = {
        loading: true,
        loaded: null,
        error: null
    };
    
    // 一个 promise 赋值, 未调用
    state.promise = promise
        .then(loaded => {
            state.loading = false;
            state.loaded = loaded;
            return loaded;
        })
        .catch(err => {
            state.loading = false;
            state.error = err;
            throw err;
        });

    return state;
}

loadMap

transfer:

Loadable.Map = LoadableMap;

function LoadableMap(opts) {
    return createLoadableComponent(loadMap, opts);
}

Specific code:

function loadMap(obj) {
    let state = {
        loading: false,
        loaded: {},
        error: null
    };

    let promises = [];

    try {
        Object.keys(obj).forEach(key => {
            let result = load(obj[key]);

            if (!result.loading) {
                state.loaded[key] = result.loaded;
                state.error = result.error;
            } else {
                state.loading = true;
            }

            promises.push(result.promise);

            result.promise
                .then(res => {
                    state.loaded[key] = res;
                })
                .catch(err => {
                    state.error = err;
                });
        });
    } catch (err) {
        state.error = err;
    }

    state.promise = Promise.all(promises)
        .then(res => {
            state.loading = false;
            return res;
        })
        .catch(err => {
            state.loading = false;
            throw err;
        });

    return state;
}

In general, similar to load, the Promise.all api is used to construct a promise array result

Summarize

Look at the structure from the component:

Loadable() === createLoadableComponent(load, opts) === class LoadableComponent

From the point of view of the call:

  1. init calls the load function, which is used to package the parameters after the component is loaded
  2. init directly returns the result of the component corresponding to the promise
  3. Render the loading component or render component according to the corresponding result in the render function
  4. The render component uses the React.createElement component to render

()=>import() , it is still a relatively simple component to remove SSR related, and the main use is still the support of 0617ebd91d046a syntax

refer to:

https://github.com/jamiebuilds/react-loadable


Grewer
984 声望28 粉丝

Developer


« 上一篇
从 JSON 说起
下一篇 »
关于 ArrayBuffer