Skip to content

vue异步组件实现原理

视频讲解

核心代码 src/libs/apiAsyncComponent.ts

typescript
import { h, shallowRef } from 'vue'

export function defineAsyncComponent(options) {
    if (typeof options === 'function') {
        options = {
            loader: options
        }
    }

    const defaultComponent = () => h('div')
    const {
        loader,
        loadingComponent = defaultComponent,
        errorComponent = defaultComponent,
        timeout
    } = options

    return {
        setup(_, { attrs, slots }) {
            /**
             * 异步组件:
             * 在不同的状态下,显示不同的组件
             */
            const component = shallowRef(loadingComponent)

            const loadComponent = () => {
                return new Promise((resolve, reject) => {
                    if (timeout) {
                        setTimeout(() => {
                            reject('超时了')
                        }, timeout)
                    }
                    loader().then(resolve, reject)
                })
            }

            loadComponent()
                .then((com) => {
                    if (com && com[Symbol.toStringTag] === 'Module') {
                        com = com.default
                    }
                    // 加载成功
                    component.value = com
                })
                .catch((err) => {
                    console.log('err', err)
                    // 加载失败,或者超时
                    component.value = errorComponent
                })

            return () => {
                return h(component.value, attrs, slots)
            }
        }
    }
}

使用 Demo

vue
<script lang="ts" setup>
import { h } from 'vue'
import { defineAsyncComponent } from '@/libs/apiAsyncComponent.ts'

const AsyncComponent = defineAsyncComponent({
    // loader: () => {
    //     return new Promise((resolve, reject) => {
    //         setTimeout(() => {
    //             const com = {
    //                 setup() {
    //                     return () => h('div', '异步组件')
    //                 }
    //             }
    //
    //             resolve(com)
    //         }, 4000)
    //     })
    // },
    loader: () => import('./components/HelloWorld.vue'),
    timeout: 3000,
    loadingComponent: {
        render: () => h('div', 'loading...')
    },
    errorComponent: {
        render: () => h('div', '加载失败...')
    }
})
</script>

<template>
    <AsyncComponent msg="app 传的 msg">
        <div>插槽</div>
    </AsyncComponent>
</template>

Released under the MIT License.