react-loadable 源码解析

简要的来说, loadable 是一个高阶函数, 他同时利用了 react 的渲染 API, webpack 知识点, babel, promise 合并起来的组件

应用

首先咱们要晓得 react-loadable 他的用法是什么:

  1. loader
    须要提早加载的组件, 应用必须要用 () => import('xxx') 语法
  2. loading
    loading 组件, props 承受 error, pastDelay , timedOut, retry参数, 能够自定义
  3. delay
    能够增加提早
  4. timeout
    超时工夫
  5. render
    类型为: (loaded, props)=>ReactNode, 能够增加额定的参数注入

多个组件同时加载

其中承受的参数 loader, render, 类型和上述差不太多

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}/>;  },})

预加载

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

库里还波及到 SSR 相干知识点, 这里就不波及了

源码

这里因为不讲 SSR 相干, 所以我在这里删除了相干代码: loadable.jsx

主体

在此高阶组件中, 他的主体是: createLoadableComponent

首先咱们看他闭包中所作的货色:

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{        // 这里先疏忽    }}

返回组件

咱们再来看返回的组件:

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) 中的入参loadFnfunction 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

调用:

Loadable.Map = LoadableMap;function LoadableMap(opts) {    return createLoadableComponent(loadMap, opts);}

具体代码:

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;}

总的来说, 和 load 相似, 利用了 Promise.all api 来构建了一个 promise 数组后果

总结

从组件来上看构造:

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

从调用上来看:

  1. init 调用 load 函数, 他用来包装 组件加载后的参数
  2. init 间接返回组件对应 promise 的后果
  3. 在 render 函数中依据对应后果渲染 loading 组件或者 render 组件
  4. render 组件利用的是 React.createElement 组件渲染

总的来说去除 SSR 相干, 还是一个比较简单的组件, 次要的利用还是 ()=>import() 语法的反对

参考:

https://github.com/jamiebuild...