异步组件

defineAsyncComponent函数是一个高阶组件,他的返回值是一个包装组件。此包装组件会根据状态来决定渲染的内容,加载成功后渲染组件,在未渲染成功时渲染一个占位符节点

基本实现

let asyncComponent = defineAsyncComponent(()=>{
    return new Promise((resolve,reject)=>{
        setTimeout(() => {
            resolve({
                render:()=>h('div','hi jiang')
            });
        }, 1000);
    })
})
1
2
3
4
5
6
7
8
9
export function defineAsyncComponent(loader){
    let Comp = null;
    return {
        setup(){
            const loaded = ref(false)
            loader().then(c=>{
                Comp = c;
                loaded.value = true;
            })
            return ()=>{
                return loaded.value ? h(Comp):h(Fragment,'');
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

异步组件超时处理

let asyncComponent = defineAsyncComponent({
    loader:()=>{
        return new Promise((resolve,reject)=>{
            setTimeout(() => {
                resolve({
                    render(){
                        return h('div','hi jiang')
                    }
                });
            }, 1000);
        })
    },
    timeout:2000,
    errorComponent:{
        render:()=>h('Text','超时错误')
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export function defineAsyncComponent(options){
    if(typeof options === 'function'){
        options = { loader:options }
    }
    let Comp = null;
    return {
        setup(){
            const {loader} = options;
            const loaded = ref(false);
            const error = ref(false); // 是否超时
            loader().then(c=>{
                Comp = c;
                loaded.value = true;
            }).catch((err)=>error.value = err)
            if(options.timeout){ 
                setTimeout(()=>{
                    error.value = true; // 显示错误组件
                },options.timeout)
            }
            const placeHolder = h(Fragment,'')
            return ()=>{
                if(loaded.value){
                    return h(Comp);
                }else if(error.value && options.errorComponent){ // 超时显示错误组件
                    return h(options.errorComponent)
                }
                return placeHolder
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

组件卸载的时候需要稍作处理

const unmount = (vnode) =>{
    const {shapeFlag} = vnode;
    if(vnode.type === Fragment){
        return unmountChildren(vnode.children)
    }else if(shapeFlag & ShapeFlags.COMPONENT){
        // 组件那么移除
        return unmount(vnode.component.subTree); // 移除组件
    }
    hostRemove(vnode.el)
}
1
2
3
4
5
6
7
8
9
10

异步组件loading处理

let asyncComponent = defineAsyncComponent({
    loader:()=>{
        return new Promise((resolve,reject)=>{
            setTimeout(() => {
                resolve({
                    render(){
                        return h('div','hi jiang')
                    }
                });
            }, 3000);
        })
    },
    timeout:2000,
    errorComponent:{
        render:()=>h('Text','超时错误')
    },
    delay:1000,
    loadingComponent:{
        render: ()=> h('h2','loading....')
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// loading逻辑
const loading = ref(false);
let loadingTimer = null;
if(options.delay){
    loadingTimer = setTimeout(()=>{
        loading.value = true;
    },options.delay)
}else{
    loading.value = true;
}
const error = ref(false); 
loader().then(c=>{
    Comp = c;
    loaded.value = true;
}).catch((err)=>error.value = err).finally(()=>{
    loading.value = false;
    clearTimeout(loadingTimer); // 加载完毕的时候清理定时器
})
// ...
return ()=>{
    if(loaded.value){
        return h(Comp);
    }else if(error.value && options.errorComponent){ 
        return h(options.errorComponent)
    }else if(loading.value && options.loadingComponent){ // 显示loading组件
        return h(options.loadingComponent)
    }
    return placeHolder
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

异步组件加载重试处理

let asyncComponent = defineAsyncComponent({
    loader:()=>{
        return new Promise((resolve,reject)=>{
            setTimeout(() => {
                reject({
                    render(){
                        return h('div','hi jiang')
                    }
                });
            }, 3000);
        })
    },
    timeout:2000,
    errorComponent:{
        render:()=>h('Text','超时错误')
    },
    delay:1000,
    loadingComponent:{
        render: ()=> h('h2','loading....')
    },
    onError(retry){
        console.log('错了')
        retry()
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function load(){
    return loader().catch(err=>{
        if(options.onError){
            return new Promise((resolve,reject)=>{
                const retry = ()=>resolve(load());
                const fail = ()=>reject(err);
                options.onError(retry,fail)
            });
        }else{
            throw err;
        }
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13