手把手教您實(shí)現(xiàn)react異步加載高階組件
本篇文章通過分析react-loadable包的源碼,手把手教你實(shí)現(xiàn)一個(gè)react的異步加載高階組件
1. 首先我們想象中的react異步加載組件應(yīng)該如何入?yún)⒁约氨┞赌男〢PI?
// 組件應(yīng)用
import * as React from 'react';
import ReactDOM from 'react-dom';
import Loadable from '@component/test/Loadable';
import Loading from '@component/test/loading';
const ComponentA = Loadable({
loader: () => import(
/* webpackChunkName: 'componentA' */
'@component/test/componentA.js'),
loading: Loading, //異步組件未加載之前l(fā)oading組件
delay: 1000, //異步延遲多久再渲染
timeout: 1000, //異步組件加載超時(shí)
})
ComponentA.preload(); //預(yù)加載異步組件的方式
const ComponentB = Loadable({
loader: () => import(
/* webpackChunkName: 'componentB' */
'@component/test/componentB.js'),
loading: Loading, //異步組件未加載之前l(fā)oading組件
})
Loadable.preloadAll().then(() => {
//
}).catch(err => {
//
}); //預(yù)加載所有的異步組件
const App = (props) => {
const [isDisplay, setIsDisplay] = React.useState(false);
if(isDisplay){
return <React.Fragment>
<ComponentA />
<ComponentB />
</React.Fragment>
}else{
return <input type='button' value='點(diǎn)我' onClick={()=>{setIsDisplay(true)}}/>
}
}
ReactDOM.render(<App />, document.getElementById('app'));
// loading組件
import * as React from 'react';
export default (props) => {
const {error, pastDelay, isLoading, timedOut, retry} = props;
if (props.error) {
return <div>Error! <button onClick={ retry }>Retry</button></div>;
} else if (timedOut) {
return <div>Taking a long time... <button onClick={ retry }>Retry</button></div>;
} else if (props.pastDelay) {
return <div>Loading...</div>;
} else {
return null;
}
}
通過示例可以看到我們需要入?yún)oaded、loading、delay、timeout,同時(shí)暴露單個(gè)預(yù)加載和全部預(yù)加載的API,接下來就讓我們試著去一步步實(shí)現(xiàn)Loadable高階組件
2.組件實(shí)現(xiàn)過程
整個(gè)Loaded函數(shù)大體如下
// 收集所有需要異步加載的組件 用于預(yù)加載
const ALL_INITIALIZERS = [];
function Loadable(opts){
return createLoadableComponent(load, opts);
}
// 靜態(tài)方法 預(yù)加載所有組件
Loadable.preloadAll = function(){
}
接下來實(shí)現(xiàn)createLoadableComponent以及l(fā)oad函數(shù)
// 預(yù)加載單個(gè)異步組件
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;
}
// 創(chuàng)建異步加載高階組件
function createLoadableComponent(loadFn, options){
if (!options.loading) {
throw new Error("react-loadable requires a `loading` component");
}
let opts = Object.assign({
loader: null,
loading: null,
delay: 200,
timeout: null,
}, options);
let res = null;
function init(){
if(!res){
res = loadFn(options.loader);
return res.promise;
}
}
ALL_INITIALIZERS.push(init);
return class LoadableComponent extends React{}
}
我們可以看到createLoadableComponent主要功能包括合并默認(rèn)配置,將異步組件推入預(yù)加載數(shù)組,并返回LoadableComponent組件;load函數(shù)用于加載單個(gè)組件并返回該組件的初始加載狀態(tài)
接著我們實(shí)現(xiàn)核心部分LoadableComponent組件
class LoadableComponent extends React.Component{
constructor(props){
super(props);
//組件初始化之前調(diào)用init方法下載異步組件
init();
this.state = {
error: res.error,
postDelay: false,
timedOut: false,
loading: res.loading,
loaded: res.loaded
}
this._delay = null;
this._timeout = null;
}
componentWillMount(){
//設(shè)置開關(guān)保證不多次去重新請求異步組件
this._mounted = true;
this._loadModule();
}
_loadModule(){
if(!res.loading) return;
if(typeof opts.delay === 'number'){
if(opts.delay === 0){
this.setState({pastDelay: true});
}else{
this._delay = setTimeout(()=>{
this.setState({pastDelay: true});
}, opts.delay)
}
}
if(typeof opts.timeout === 'number'){
this._timeout = setTimeout(()=>{
this.setState({timedOut: true});
}, opts.timeout)
}
let update = () => {
if(!this._mounted) return;
this.setState({
error: res.error,
loaded: res.loaded,
loading: res.loading,
});
}
// 接收異步組件的下載結(jié)果并重新setState來render
res.promise.then(()=>{
update()
}).catch(err => {
update()
})
}
// 重新加載異步組件
retry(){
this.setState({
error: null,
timedOut: false,
loading: false,
});
res = loadFn(opts.loader);
this._loadModule();
}
// 靜態(tài)方法 單個(gè)組件預(yù)加載
static preload(){
init()
}
componentWillUnmount(){
this._mounted = false;
clearTimeout(this._delay);
clearTimeout(this._timeout);
}
render(){
const {loading, error, pastDelay, timedOut, loaded} = this.state;
if(loading || error){
//異步組件還未下載完成的時(shí)候渲染loading組件
return React.createElement(opts.loading, {
isLoading: loading,
pastDelay: pastDelay,
timedOut: timedOut,
error: error,
retry: this.retry.bind(this),
})
}else if(loaded){
// 為何此處不直接用React.createElement?
return opts.render(loaded, this.props);
}else{
return null;
}
}
}
可以看到,初始的時(shí)候調(diào)用init方法啟動(dòng)異步組件的下載,并在_loadModule方法里面接收異步組件的pending結(jié)果,待到異步組件下載完畢,重新setState啟動(dòng)render
接下來還有個(gè)細(xì)節(jié),異步組件并沒有直接啟動(dòng)React.createElement去渲染,而是采用opts.render方法,這是因?yàn)閣ebpack打包生成的單獨(dú)異步組件chunk暴露的是一個(gè)對象,其default才是對應(yīng)的組件
實(shí)現(xiàn)如下
function resolve(obj) {
return obj && obj.__esModule ? obj.default : obj;
}
function render(loaded, props) {
return React.createElement(resolve(loaded), props);
}
最后實(shí)現(xiàn)全部預(yù)加載方法
Loadable.preloadAll = function(){
let promises = [];
while(initializers.length){
const init = initializers.pop();
promises.push(init())
}
return Promise.all(promises);
}
整個(gè)代碼實(shí)現(xiàn)如下
const React = require("react");
// 收集所有需要異步加載的組件
const ALL_INITIALIZERS = [];
// 預(yù)加載單個(gè)異步組件
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;
}
function resolve(obj) {
return obj && obj.__esModule ? obj.default : obj;
}
function render(loaded, props) {
return React.createElement(resolve(loaded), props);
}
// 創(chuàng)建異步加載高階組件
function createLoadableComponent(loadFn, options){
if (!options.loading) {
throw new Error("react-loadable requires a `loading` component");
}
let opts = Object.assign({
loader: null,
loading: null,
delay: 200,
timeout: null,
render,
}, options);
let res = null;
function init(){
if(!res){
res = loadFn(options.loader);
return res.promise;
}
}
ALL_INITIALIZERS.push(init);
class LoadableComponent extends React.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(typeof opts.delay === 'number'){
if(opts.delay === 0){
this.setState({pastDelay: true});
}else{
this._delay = setTimeout(()=>{
this.setState({pastDelay: true});
}, opts.delay)
}
}
if(typeof opts.timeout === 'number'){
this._timeout = setTimeout(()=>{
this.setState({timedOut: true});
}, opts.timeout)
}
let update = () => {
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();
}
static preload(){
init()
}
componentWillUnmount(){
this._mounted = false;
clearTimeout(this._delay);
clearTimeout(this._timeout);
}
render(){
const {loading, error, pastDelay, timedOut, loaded} = this.state;
if(loading || error){
return React.createElement(opts.loading, {
isLoading: loading,
pastDelay: pastDelay,
timedOut: timedOut,
error: error,
retry: this.retry.bind(this),
})
}else if(loaded){
return opts.render(loaded, this.props);
}else{
return null;
}
}
}
return LoadableComponent;
}
function Loadable(opts){
return createLoadableComponent(load, opts);
}
function flushInitializers(initializers){
}
Loadable.preloadAll = function(){
let promises = [];
while(initializers.length){
const init = initializers.pop();
promises.push(init())
}
return Promise.all(promises);
}
export default Loadable;
到此這篇關(guān)于手把手教您實(shí)現(xiàn)react異步加載高階組件的文章就介紹到這了,更多相關(guān)react異步加載高階組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React Native項(xiàng)目框架搭建的一些心得體會(huì)
React Native使你能夠在Javascript和React的基礎(chǔ)上獲得完全一致的開發(fā)體驗(yàn),構(gòu)建世界一流的原生APP。接下來通過本文給大家分享React Native項(xiàng)目框架搭建的一些心得體會(huì),感興趣的朋友跟隨小編一起看看吧2021-05-05
React?+?Typescript領(lǐng)域初學(xué)者的常見問題和技巧(最新)
這篇文章主要介紹了React?+?Typescript領(lǐng)域初學(xué)者的常見問題和技巧,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06
react-native 封裝選擇彈出框示例(試用ios&android)
本篇文章主要介紹了react-native 封裝選擇彈出框示例(試用ios&android),具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07
React實(shí)現(xiàn)類似淘寶tab居中切換效果的示例代碼
這篇文章主要介紹了React實(shí)現(xiàn)類似淘寶tab居中切換效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
詳解React中傳入組件的props改變時(shí)更新組件的幾種實(shí)現(xiàn)方法
這篇文章主要介紹了詳解React中傳入組件的props改變時(shí)更新組件的幾種實(shí)現(xiàn)方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-09-09
使用React+ts實(shí)現(xiàn)無縫滾動(dòng)的走馬燈詳細(xì)過程
這篇文章主要給大家介紹了關(guān)于使用React+ts實(shí)現(xiàn)無縫滾動(dòng)的走馬燈詳細(xì)過程,文中給出了詳細(xì)的代碼示例以及圖文教程,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-08-08
React創(chuàng)建組件的三種方式及其區(qū)別
本文主要介紹了React創(chuàng)建組件的三種方式及其區(qū)別,具有一定的參考價(jià)值,下面跟著小編一起來看下吧2017-01-01

