react+webpack对于react开发,相信已经是一个大众套餐了,至于其他的parcel或者rollup或者其他一些构建框架我也没仔细用过,也不太熟,听说parcel上github短时间内已经上万颗星了,很流弊的样纸,不过这都不是我们本文重点,呵呵。本文重点是模块的异步加载,这里不谈其他的,只谈谈按需加载优化。使用webpack构建的时候,我们会把公共资源打到vendor文件中去,以便让我们的业务文件瘦身,但是我们的业务可能会有大量子业务,可能某些业务在某些用户那里永远都不会用到,这种情况下,如果我们把所有业务代码一次性全部加载下来,不是太浪费太奢侈了吗,好在webpack自身提供了require.ensure()函数,不过,这种优化方式又不在我们本次探讨范围内,本次我们来介绍一下本人心目中比较高大上的react异步加载组件。就算不喜欢,学习一下也是极好的事情啦。 我们来看以下这段代码,若不采用异步模块加载,page1和page2会合并到一个业务文件中,我要是永远不进入/buy路由,这不是浪费加载吗,那page2最好还是做成异步组件吧,那具体应该怎么做呢?
<Route path="/" component={App}>
<IndexRoute component={page1}/>
<Route path="/buy" component={page2}/>
</Route>
复制代码
好咯,不就是个异步组件吗,那不是很简单的吗,不就类似下面这样就行了的:
let page2 = ()=>{
let page2Comp = import('lazyComp');
return {
<div>
<page2Comp />
</div>
}
}
复制代码
哎呀嘛,这智商也是忒高了,以为自己多流弊啊,寥寥几行就实现了一个异步组件,天真!可惜就是报错了。不知道为啥?看看import()这玩意儿返回的是啥好不好,人家返回的是个promise对象,至少得先处理一下才好吧。这就好比你请人家上你家吃酒席,你至少得先安排人位子啊,先拿条椅子占个坑吧,等到人家来了才想起来搬凳子,任谁都不开心,掉头就走啦。那怎么给占个坑呢?其实很简单的道理,大家肯定都很熟悉的啦,请看下面一个小栗子。
class MyComp extends React.Component{
constructor(){
return {
isLoaded:false
}
}
render(){
let { isLoaded, data } = this.state;
if(!isLoaded){
return null;
}
return <Wrapper data={data} />
}
componentDidMount(){
http.getData({}).then((results)=>{
this.setState({
data:results.data,
isLoaded:true
})
})
}
}
复制代码
这段代码大家都挺熟悉了吧,数据没返回之前,不做具体渲染,直到数据返回,才开始渲染。只不过异步组件在这里有所区别的是,获取的数据就是组件本身,组件未获取到前怎么办呢?简单,用一个空元素占个位不就OK了嘛。接下来我们来看一下webpack官网给出来的一个异步组件实栗:
class LazilyLoad extends React.Component {
constructor() {
super(...arguments);
this.state = {
isLoaded: false,
};
}
componentDidMount() {
this._isMounted = true;
this.load();
}
componentDidUpdate(previous) {
if (this.props.modules === previous.modules) return null;
this.load();
}
componentWillUnmount() {
this._isMounted = false;
}
load() {
this.setState({
isLoaded: false,
});
const { modules } = this.props;
const keys = Object.keys(modules);
Promise.all(keys.map((key) => modules[key]()))
.then((values) => (keys.reduce((agg, key, index) => {
agg[key] = values[index];
return agg;
}, {})))
.then((result) => {
if (!this._isMounted) return null;
this.setState({ modules: result, isLoaded: true });
});
}
render() {
if (!this.state.isLoaded) return <div className="toast toast-show">
<Loading/>
</div>;
console.log("modules:",this.state.modules);
return React.Children.only(this.props.children(this.state.modules));
}
}
复制代码
是不是觉得跟上面异步加载数据的栗子十分有血缘关系呢?接下来,我们具体来看一下这段代码,其他地方就不多说了,或许有些同学可能看不太明白render函数中的return React.Children.only(this.props.children(this.state.modules));
这一句代码,这种渲染方式叫做回调渲染。稍微给大家做个分析,我们先来看看以上组件的调用示例代码:
const LazilyLoadFactory = (Component, modules) => {
console.log("LazilyLoadFactory");
return (props) => (
<LazilyLoad modules={modules}>
{(mods) => <Component {...mods} {...props} />}
</LazilyLoad>
);
};
复制代码
如果还是不太理解,先把上面return React.Children.only(this.props.children(this.state.modules));
这句代码中的几个元素拆解一下:
- this.props.children:这个大家应该都懂啥意思了,指代调用组件的子元素,以上示例中,不就是指代
(mods) => <Component {...mods} {...props} />
这个函数嘛 - this.state.modules:这个就是LazilyLoad组件中传入props的modules变量被处理成state的变量
- React.Children.only:就不必说了吧,想必都比较熟悉了
这样稍微拆解后,是不是就很清晰了呢,回过头来看一下我们的stateless组件LazilyLoadFactory,渲染的是LazilyLoad组件,使用回调渲染的方式实际上以参数modules作为props入参对参数组件Component进行渲染,那就很明显了,参数组件Component就是这个异步组件的占坑板凳了,我们来看看这个作为参数传入的组件的具体代码:
class WrapLazyComp extends React.Component{
render(){
const Comp = this.props.Comp;
return <div>
<Comp />
</div>;
}
}
复制代码
好了,然后接下来就是我们的总调用方了
LazilyLoadFactory(WrapLazyComp,{
Comp: () => import('实际业务模块')
});
复制代码
到此为止,我们的异步组件整个就完成了,主要就是利用import()实现对模块的异步加载,可能有些同学对于回调渲染可能会有些模糊,不熟悉的可能稍微需要了解了解。 个人比较喜欢这种方式进行异步模块的加载,当然还有类似require.ensure等等此类的方法,具体优化方式视个人偏好以及项目具体情况,也不能一概而论。 好了,感谢各位,如有错误,请多多指教。