在作我的网站的js拆分打包时,最终的解决方案是看着网上的教程手写了Bundle高阶组件来动态加载须要的组件。对于它的运用也仅仅是把路由拆开,访问不一样的顶级路由进行动态加载,并无对其原理进行深刻的理解。直到看到了React 的加载 loading 库——react-loadable。react
Loadable提倡基于组件分割代码。 webpack
route-centric code splitting is shit, component-centric splitting is cool as shit.git
网上的翻译有不少,本文就不过多对readme操做了,简单的介绍一下就是: Loadable 是一个高阶组件(简单来讲,就是把组件做为输入的组件。高阶函数就是把函数做为输入的函数。在 React 里,函数和组件有时是一回事),一个能够构建组件的函数(函数能够是组件),它能够很容易的在组件层面分割代码包。github
下面是github库里readme给的例子。web
import Loadable from 'react-loadable';
import Loading from './my-loading-component';
const LoadableComponent = Loadable({
loader: () => import('./my-component'), // 要按需加载的组件,用了import()函数
loading: Loading, // 一个无状态组件,负责显示"Loading中"
});
export default class App extends React.Component {
render() {
return <LoadableComponent/>;
}
}
复制代码
import React from 'react';
export default function Loading(props) {
if (props.isLoading) {
if (props.timedOut) {
return <div>Loader timed out!</div>;
} else if (props.pastDelay) {
return <div>Loading...</div>;
} else {
return null;
}
} else if (props.error) {
return <div>Error! Component failed to load</div>;
} else {
return null;
}
}
复制代码
这是loadable的源码结构 promise
最后export出来的是一个Loadable
函数,因此咱们从Loadable
开始分析。bash
module.exports = Loadable;
function Loadable(opts) {
return createLoadableComponent(load, opts);
}
复制代码
Loadable接受一个参数opts
,再调用了createLoadableComponent
函数,传入了load函数和opts。异步
function load(loader) {
let promise = loader();
let state = {
loading: true,
loaded: null,
error: null
};
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;
}
复制代码
这里load
又是一个函数,接受一个loader
参数。咱们先放在这里,一会在回来看这个loader
。函数
这个函数是整个库的毕竟重要的函数了。(由于本文是浅析,因此先只分析主逻辑的函数,别的健壮性支持函数可能会以后再分析)源码分析
options.loading
就是上文提到的一个无状态组件,负责显示"Loading中",若是不存在,会报错,须要一个Loading中显示的组件。
let opts = Object.assign({
loader: null,
loading: null,
delay: 200,
timeout: null,
render: render,
webpack: null,
modules: null,
}, options);
复制代码
就是初始化一下opts的值,赋给未传入参数初始值,防止接下来的判断报错。
function init() {
if (!res) {
res = loadFn(opts.loader);
}
return res.promise;
}
复制代码
init
以前声明过一个res
,init
就是为res
赋值。在init
里调用了loadFn
,就是上文说过的load函数
。
load函数接收一个参数,就是以前() => import('./my-component')
,这里的import()
方法,返回一个Promise对象,[[PromiseValue]].default
就是待load组件的function。
load函数里初始化了一个state对象,并在import()方法返回的Promise中,对state的属性赋值,loading
表明是否加载完成, loaded
为加载的对象,这里的state.loaded
已是[[PromiseValue]]
了。
由于react loadable的描述终究是一个高阶组件,若是对高阶组件不了解的,能够去看 深刻React高阶组件(HOC)这篇文章。 因此createLoadableComponent
最后返回的是一个React Component。
一开始的constructor
里,调用了init,将res的几个属性,分别赋值给this.state做为组件初始化。
this.state = {
error: res.error,
pastDelay: false,
timedOut: false,
loading: res.loading,
loaded: res.loaded
};
复制代码
以后在componentWillMount
中,作了这些操做
this._mounted
,默认为trueres.loading
为true,说明以前的init执行出错,直接return;opts.delay
和opts.timeout
有值,且为number属性的话,就加个定时器。this._mounted
为false,从新将res的属性更新到state里。render
中进行了判断,
this.state.loading
或者this.state.error
为true,就是总体状态是正在加载ing或者出错了,就用React.createElement
生成出loading的过渡组件,this.state.loaded
有值了,说明传入的loader
的promise异步操做执行完成,就开始渲染真正的组件,调用opts.render
方法.(此时的this.state.loaded就是[[PromiseValue]]
)
function resolve(obj) {
return obj && obj.__esModule ? obj.default : obj;
}
function render(loaded, props) {
return React.createElement(resolve(loaded), props);
}
复制代码
render的时候进行判断,当__esModule为true的时候,标识module为es模块,那么obj默认返回obj.default,那么obj默认返回obj。以后再调用React.createElement
进行渲染。
至于props是什么?在项目里打印发现,就是原组件的props。因此render出的,就和正常在代码中写的React Component是同样的。
这个库的设计仍是很是巧妙,利用import返回一个promise对象的性质,进行loading的异步操做,有点像图片懒加载的原理。下面是划重点系列,
本文只是对react loadable
这个库的核心源代码的分析,还有一些别的代码没有分析到,以及一些分析失误的地方,都欢迎你们交流分享。能够发邮件给我:uiryzd@163.com