Suspense 基础使用
参考文章: vueschool.io/articles/vu…
官网暂未放出 Suspense 文档,因为属于 feature,API 还未稳定。使用时还会弹出提示。
is an experimental feature and its API will likely change.
已知 Suspense 可以对异步的 setup 组件,提供 fallback 的 slot。
又已知 defineAsyncComponent 提供异步组件懒加载功能。功能非常相近。并且文档中有提到 defineAsyncComponent 可与 Suspense 一起使用。不过部分 options 会无效。文档
与
Suspense一起使用 异步组件在默认情况下是可挂起的。这意味着如果它在父链中有一个<Suspense>,它将被视为该<Suspense>的异步依赖。 在这种情况下,加载状态将由<Suspense>控制,组件自身的加载、错误、延迟和超时选项将被忽略。
所以要使用的话,要抽象出公共一个方法,简化调用。
实现要点
- import 函数的动态路径引入
- 实现
delay,timeout,error,loading - 高阶组件的
props、event、slots,ref的透传 - 可
retry
部分主要实现代码
function createInnerComp(
comp,
{
vnode: { ref, props, children }
}
) {
const compVnode = h(comp, props, children)
// ref 透传
compVnode.ref = ref
return compVnode
}
function asyncLoader (componentPath, options = {}) {
return {
name: 'asyncLoaderWrapper',
emits: ['resolve', 'fallback', 'pending'],
inheritAttrs: false,
/* 骗过上帝
https://github.com/vuejs/core/blob/main/packages/runtime-core/src/rendererTemplateRef.ts#L43
setRef 时,通过 __asyncLoader 字段判断来跳过该组件 ref 的设置。再通过 render 函数把 ref 字段赋值给我们想要透传的组件的 vnode 即可完成 forWardRef
*/
__asyncLoader: Promise.resolve(),
setup(props, { emit }) {
const { retry, error, setComponentLoadStatus } = useComponentStatus()
const {
errorComponent,
loadingComponent,
delay,
...defineAsyncOptions
} = { ...asyncLoaderDefaultOptions, ...pluginOptions, ...options, }
const instance = getCurrentInstance()
const optionsComponent = normalizeSuspenseDefaultSFC(componentPath, {
...defineAsyncOptions,
onComponentLoadStatus: setComponentLoadStatus
})
return () => {
if (error.value) {
return h(errorComponent, { error: error.value, retry })
}
const defaultChildVnode = createInnerComp(optionsComponent, instance)
const fallbackVnode = h({
props: {
loadingDelay: Number
},
setup(props) {
// patch 的时候 container 是 null.
// component effect 的时候, prevTree = instance.subTree 居然是 comment 节点
// hostParentNode(prevTree.el) 寻找父节作为 patch 的 container 为 null. 导致插入节点失败
// 原因: fallback 的 rerender 后. Suspense 没有更新 subTree.
// vue3 slot 只能加载 templte 上. 除非只有 defualt 插槽
const { delayed } = useDelay(props.loadingDelay)
return () => !delayed.value ? h(loadingComponent) : null
}
}, {
loadingDelay: delay
})
return h(Suspense, {
onFallback(...args) {
emit('fallback', ...args)
},
onResolve(...args) {
emit('resolve', ...args)
},
onPending(...args) {
emit('pending', ...args)
},
}, {
default: wrapTemplate(defaultChildVnode),
// fallback 变动好像会导致 default 重新渲染, delay 只能放 fallback 里执行
fallback: wrapTemplate(fallbackVnode)
})
}
}
}
}
演示
完整代码
能力有限。如果哪里写的不好,欢迎指点一下
TODO
支持 typescipt,增加类型定义