专栏首页纸上得来终觉浅Vue3源码阅读笔记之异步组件
原创

Vue3源码阅读笔记之异步组件

// 看下vue的异步组如何实现的

// implementation, close to no-op
// 外部调用API
function defineComponent(options) {
    return isFunction(options) ? { setup: options, name: options.name } : options;
}

const isAsyncWrapper = (i) => !!i.type.__asyncLoader;
// 主要实现在这里
function defineAsyncComponent(source) {
    // 统一参数
    if (isFunction(source)) {
        source = { loader: source };
    }
    // 我们平常使用的异步组件的主要参数
    const { loader, loadingComponent, errorComponent, delay = 200, timeout, // undefined = never times out
    suspensible = true, onError: userOnError } = source;
    let pendingRequest = null;
    let resolvedComp;
    let retries = 0;
    // 重新尝试load得到组件内容
    const retry = () => {
        retries++;
        pendingRequest = null;
        return load();
    };
    
    const load = () => {
        let thisRequest;
        return (pendingRequest ||
            (thisRequest = pendingRequest = loader()
                .catch(err => {
                // 失败场景
                err = err instanceof Error ? err : new Error(String(err));
                if (userOnError) {
                    return new Promise((resolve, reject) => {
                        const userRetry = () => resolve(retry());
                        const userFail = () => reject(err);
                        // 对应文档中的 失败捕获函数 用户自己决定如何使用
                        userOnError(err, userRetry, userFail, retries + 1);
                    });
                }
                else {
                    throw err;
                }
            })
                .then((comp) => {
                // 成功情况
                if (thisRequest !== pendingRequest && pendingRequest) {
                    return pendingRequest;
                }
                if (!comp) {
                    warn(`Async component loader resolved to undefined. ` +
                        `If you are using retry(), make sure to return its return value.`);
                }
                // interop module default
                if (comp &&
                    (comp.__esModule || comp[Symbol.toStringTag] === 'Module')) {
                    comp = comp.default;
                }
                if (comp && !isObject(comp) && !isFunction(comp)) {
                    throw new Error(`Invalid async component load result: ${comp}`);
                }
                // 闭包无处不在
                resolvedComp = comp;
                return comp;
            })));
    };
    return defineComponent({
        __asyncLoader: load,
        // 异步组件统一名字
        name: 'AsyncComponentWrapper',
        // 组件有setup的走setup逻辑
        setup() {
            const instance = currentInstance;
            // already resolved
            if (resolvedComp) {
                return () => createInnerComp(resolvedComp, instance);
            }
            const onError = (err) => {
                pendingRequest = null;
                handleError(err, instance, 13 /* ASYNC_COMPONENT_LOADER */, !errorComponent /* do not throw in dev if user provided error component */);
            };
            // suspense-controlled or SSR.
            // 对应文档中如果父组件是一个 suspense 那么只返回promise结果 其余的控制交给 suspense 处理即可
            if ((suspensible && instance.suspense) ||
                (false )) {
                return load()
                    .then(comp => {
                    return () => createInnerComp(comp, instance);
                })
                    .catch(err => {
                    onError(err);
                    return () => errorComponent
                        ? createVNode(errorComponent, {
                            error: err
                        })
                        : null;
                });
            }
            // loaded 是响应式的
            const loaded = ref(false);
            const error = ref();
            const delayed = ref(!!delay);
            // 对应文档中描述的2个控制
            if (delay) {
                setTimeout(() => {
                    delayed.value = false;
                }, delay);
            }
            if (timeout != null) {
                setTimeout(() => {
                    if (!loaded.value && !error.value) {
                        const err = new Error(`Async component timed out after ${timeout}ms.`);
                        onError(err);
                        error.value = err;
                    }
                }, timeout);
            }
            load()
                .then(() => {
                // promise成功返回后触发trigger导致组件更新 重新渲染组件 只不过此时我们已经得到组件内容了
                loaded.value = true;
            })
                .catch(err => {
                onError(err);
                error.value = err;
            });
            // 返回的函数会被当做组件实例的 render 函数
            return () => {
                // render初始执行触发 loaded的依赖收集 
                if (loaded.value && resolvedComp) {
                    return createInnerComp(resolvedComp, instance);
                }
                else if (error.value && errorComponent) {
                    return createVNode(errorComponent, {
                        error: error.value
                    });
                }
                else if (loadingComponent && !delayed.value) {
                    return createVNode(loadingComponent);
                }
                // 返回undefined 会创建一个注释占位节点
            };
        }
    });
}
// 根据返回组件内容得到vnode 就可以正常按照组件vnode的方式渲染了 不再是一个promise了
function createInnerComp(comp, { vnode: { ref, props, children } }) {
    const vnode = createVNode(comp, props, children);
    // ensure inner component inherits the async wrapper's ref owner
    vnode.ref = ref;
    return vnode;
}

// 总结一下:vue3的异步组件写的非常清晰明了,十分好理解

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Vue3源码阅读笔记之组件是如何实现

    wanyicheng
  • Vue3源码阅读笔记之事件队列

    总结一下:vue中的事件队列分3种,vue内部实现中主要是把render函数的包裹体effect放到queue队列中。

    wanyicheng
  • Vue3源码阅读笔记之$emit实现

    总结:组件实例上的 emit 方法其实就是调用props中从父组件传进来的一个箭头函数。

    wanyicheng
  • Vue3源码阅读笔记之vnode定义

    wanyicheng
  • Vue3源码阅读笔记之数据响应式

    总结:Vue3中的数据响应式实现是一个较为独立的实现,适合单独分析学习哈。上文是删除了部分支线逻辑的版本,只保留了主线逻辑,大家如果想看完整的实现,还是建议去读...

    wanyicheng
  • 写给初中级前端的高级进阶指南(JS、TS、Vue、React、性能、学习规划)

    我曾经一度很迷茫,在学了Vue、React的实战开发和应用以后,好像遇到了一些瓶颈,不知道该怎样继续深入下去。相信这也是很多一两年经验的前端工程师所遇到共同问题...

    ssh_晨曦时梦见兮
  • Vue3源码阅读笔记之整体执行顺序简介(1)

    从Vue官网得到源码(https://unpkg.com/vue@next),拷贝到本地文件,然后创建如下html:

    wanyicheng
  • Vue3源码阅读笔记之整体执行顺序简介(2)

    可以看到,目前只是直接对组件实例的data做了一次代理,handlers在普通对象情况下为 baseHandlers

    wanyicheng
  • Vue3 深度解析

    距离尤雨溪首次公开 Vue3 (vue-next)源码有一个多月了。青笔观察到,刚发布国庆期间,出现不少解读 Vue3 源码的文章。当然不少有追风蹭热之嫌,文章...

    我是一条小青蛇

扫码关注云+社区

领取腾讯云代金券