手把手教您实现react异步加载高阶组件
本篇文章通过分析react-loadable包的源码,手把手教你实现一个react的异步加载高阶组件
1.首先我们想象中的react异步加载组件应该如何入参以及暴露哪些API?
//组件应用 import*asReactfrom'react'; importReactDOMfrom'react-dom'; importLoadablefrom'@component/test/Loadable'; importLoadingfrom'@component/test/loading'; constComponentA=Loadable({ loader:()=>import( /*webpackChunkName:'componentA'*/ '@component/test/componentA.js'), loading:Loading,//异步组件未加载之前loading组件 delay:1000,//异步延迟多久再渲染 timeout:1000,//异步组件加载超时 }) ComponentA.preload();//预加载异步组件的方式 constComponentB=Loadable({ loader:()=>import( /*webpackChunkName:'componentB'*/ '@component/test/componentB.js'), loading:Loading,//异步组件未加载之前loading组件 }) Loadable.preloadAll().then(()=>{ // }).catch(err=>{ // });//预加载所有的异步组件 constApp=(props)=>{ const[isDisplay,setIsDisplay]=React.useState(false); if(isDisplay){ return}else{ return {setIsDisplay(true)}}/> } } ReactDOM.render( ,document.getElementById('app'));
//loading组件 import*asReactfrom'react'; exportdefault(props)=>{ const{error,pastDelay,isLoading,timedOut,retry}=props; if(props.error){ returnError!; }elseif(timedOut){ returnRetry Takingalongtime...; }elseif(props.pastDelay){ returnRetry Loading...; }else{ returnnull; } }
通过示例可以看到我们需要入参loaded、loading、delay、timeout,同时暴露单个预加载和全部预加载的API,接下来就让我们试着去一步步实现Loadable高阶组件
2.组件实现过程
整个Loaded函数大体如下
//收集所有需要异步加载的组件用于预加载 constALL_INITIALIZERS=[]; functionLoadable(opts){ returncreateLoadableComponent(load,opts); } //静态方法预加载所有组件 Loadable.preloadAll=function(){ }
接下来实现createLoadableComponent以及load函数
//预加载单个异步组件 functionload(loader){ letpromise=loader(); letstate={ loading:true, loaded:null, error:null, } state.promise=promise.then(loaded=>{ state.loading=false; state.loaded=loaded; returnloaded; }).catch(err=>{ state.loading=false; state.error=err; throwerr; }) returnstate; } //创建异步加载高阶组件 functioncreateLoadableComponent(loadFn,options){ if(!options.loading){ thrownewError("react-loadablerequiresa`loading`component"); } letopts=Object.assign({ loader:null, loading:null, delay:200, timeout:null, },options); letres=null; functioninit(){ if(!res){ res=loadFn(options.loader); returnres.promise; } } ALL_INITIALIZERS.push(init); returnclassLoadableComponentextendsReact{} }
我们可以看到createLoadableComponent主要功能包括合并默认配置,将异步组件推入预加载数组,并返回LoadableComponent组件;load函数用于加载单个组件并返回该组件的初始加载状态
接着我们实现核心部分LoadableComponent组件
classLoadableComponentextendsReact.Component{ constructor(props){ super(props); //组件初始化之前调用init方法下载异步组件 init(); this.state={ error:res.error, postDelay:false, timedOut:false, loading:res.loading, loaded:res.loaded } this._delay=null; this._timeout=null; } componentWillMount(){ //设置开关保证不多次去重新请求异步组件 this._mounted=true; this._loadModule(); } _loadModule(){ if(!res.loading)return; if(typeofopts.delay==='number'){ if(opts.delay===0){ this.setState({pastDelay:true}); }else{ this._delay=setTimeout(()=>{ this.setState({pastDelay:true}); },opts.delay) } } if(typeofopts.timeout==='number'){ this._timeout=setTimeout(()=>{ this.setState({timedOut:true}); },opts.timeout) } letupdate=()=>{ if(!this._mounted)return; this.setState({ error:res.error, loaded:res.loaded, loading:res.loading, }); } //接收异步组件的下载结果并重新setState来render res.promise.then(()=>{ update() }).catch(err=>{ update() }) } //重新加载异步组件 retry(){ this.setState({ error:null, timedOut:false, loading:false, }); res=loadFn(opts.loader); this._loadModule(); } //静态方法单个组件预加载 staticpreload(){ init() } componentWillUnmount(){ this._mounted=false; clearTimeout(this._delay); clearTimeout(this._timeout); } render(){ const{loading,error,pastDelay,timedOut,loaded}=this.state; if(loading||error){ //异步组件还未下载完成的时候渲染loading组件 returnReact.createElement(opts.loading,{ isLoading:loading, pastDelay:pastDelay, timedOut:timedOut, error:error, retry:this.retry.bind(this), }) }elseif(loaded){ //为何此处不直接用React.createElement? returnopts.render(loaded,this.props); }else{ returnnull; } } }
可以看到,初始的时候调用init方法启动异步组件的下载,并在_loadModule方法里面接收异步组件的pending结果,待到异步组件下载完毕,重新setState启动render
接下来还有个细节,异步组件并没有直接启动React.createElement去渲染,而是采用opts.render方法,这是因为webpack打包生成的单独异步组件chunk暴露的是一个对象,其default才是对应的组件
实现如下
functionresolve(obj){ returnobj&&obj.__esModule?obj.default:obj; } functionrender(loaded,props){ returnReact.createElement(resolve(loaded),props); }
最后实现全部预加载方法
Loadable.preloadAll=function(){ letpromises=[]; while(initializers.length){ constinit=initializers.pop(); promises.push(init()) } returnPromise.all(promises); }
整个代码实现如下
constReact=require("react"); //收集所有需要异步加载的组件 constALL_INITIALIZERS=[]; //预加载单个异步组件 functionload(loader){ letpromise=loader(); letstate={ loading:true, loaded:null, error:null, } state.promise=promise.then(loaded=>{ state.loading=false; state.loaded=loaded; returnloaded; }).catch(err=>{ state.loading=false; state.error=err; throwerr; }) returnstate; } functionresolve(obj){ returnobj&&obj.__esModule?obj.default:obj; } functionrender(loaded,props){ returnReact.createElement(resolve(loaded),props); } //创建异步加载高阶组件 functioncreateLoadableComponent(loadFn,options){ if(!options.loading){ thrownewError("react-loadablerequiresa`loading`component"); } letopts=Object.assign({ loader:null, loading:null, delay:200, timeout:null, render, },options); letres=null; functioninit(){ if(!res){ res=loadFn(options.loader); returnres.promise; } } ALL_INITIALIZERS.push(init); classLoadableComponentextendsReact.Component{ constructor(props){ super(props); init(); this.state={ error:res.error, postDelay:false, timedOut:false, loading:res.loading, loaded:res.loaded } this._delay=null; this._timeout=null; } componentWillMount(){ this._mounted=true; this._loadModule(); } _loadModule(){ if(!res.loading)return; if(typeofopts.delay==='number'){ if(opts.delay===0){ this.setState({pastDelay:true}); }else{ this._delay=setTimeout(()=>{ this.setState({pastDelay:true}); },opts.delay) } } if(typeofopts.timeout==='number'){ this._timeout=setTimeout(()=>{ this.setState({timedOut:true}); },opts.timeout) } letupdate=()=>{ if(!this._mounted)return; this.setState({ error:res.error, loaded:res.loaded, loading:res.loading, }); } res.promise.then(()=>{ update() }).catch(err=>{ update() }) } //重新加载异步组件 retry(){ this.setState({ error:null, timedOut:false, loading:false, }); res=loadFn(opts.loader); this._loadModule(); } staticpreload(){ init() } componentWillUnmount(){ this._mounted=false; clearTimeout(this._delay); clearTimeout(this._timeout); } render(){ const{loading,error,pastDelay,timedOut,loaded}=this.state; if(loading||error){ returnReact.createElement(opts.loading,{ isLoading:loading, pastDelay:pastDelay, timedOut:timedOut, error:error, retry:this.retry.bind(this), }) }elseif(loaded){ returnopts.render(loaded,this.props); }else{ returnnull; } } } returnLoadableComponent; } functionLoadable(opts){ returncreateLoadableComponent(load,opts); } functionflushInitializers(initializers){ } Loadable.preloadAll=function(){ letpromises=[]; while(initializers.length){ constinit=initializers.pop(); promises.push(init()) } returnPromise.all(promises); } exportdefaultLoadable;
到此这篇关于手把手教您实现react异步加载高阶组件的文章就介绍到这了,更多相关react异步加载高阶组件内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!