一、异步组件
1、为什么要封装
-
节省用户的工作量,
-
能够提供拓展功能
封装前
<template>
<component :is="asyncComponent"/>
</template>
<script>
import { shallowRef } from "vue"
export default {
setup(props, setupContext) {
const asyncComponent = ref(null);
import("CompB.vue").then(CompB => asyncComponent.value = CompB)
return {
asyncComponent
}
}
}
</script>
封装后
<template>
<AsyncComp/>
</template>
<script>
export default {
components: {
AsyncComp: defineAsyncComponent(() => import("CompB"))
}
}
</script>
注意:
- defineAsynComponent函数本质是一个高阶函数, 返回值是一个包装组件
- 包装组件会根据加载器的状态决定渲染什么内容, 如果加载器成功加载了组件, 则渲染组件, 否则渲染一个占位符
- 占位内容是一个注释节点。组件没有加载成功时, 页面会渲染一个注释节点来占位, 这里使用了一个空文本节点来占位
2、封装的功能有哪些
- 超时
- Error组件
- loading组件与延迟
- 重试机制(思路值得参考)
function load(onError) {
let p = fetch();
return p.catch(err => {
return new Promise((resolve, reject) => {
const retry = () => resolve(load(onError))
const fail = () => reject(err);
onError(retry, fail);
})
})
}
Promise.resolve(1).catch(err => console.log("err", err)).then(res => console.log("res", res)) // 1
// Promise中resolve或者reject为空, 则会传递给下个then方法
报错之后通过onError判断是否重连还是结束
3、实现代码
3.1、defineAsyncComponent
function load(options, times = 0) {
let p = options.loader();
return p.catch(err => {
if(options.onError) {
times = times + 1;
return new Promise((resolve, reject) => {
const retry = () => resolve(load(options, times))
const fail = () => reject(err);
options.onError(retry, fail, times);
})
} else {
throw err;
}
})
}
function defineAsyncComponent(options) {
if(typeof options === "function") {
options = {
loader: options
}
}
let InnerComp = null;
return {
name: "defineAsyncComponent",
setup() {
let loaded = ref(false);
let loading = ref(false);
let error = shallowRef("");
let loadTimer = null;
let timeoutTimer = null;
if(options.delay) {
loadTimer = setTimeout(() => {
loading.value = true;
}, options.delay)
} else {
loading.value = true;
}
load().then(c => {
InnerComp = c;
loaded.value = true;
}).catch((err) => err.value = err
).finally(() => {
loading.value = false;
clearTimeout(loadTimer) //不管成功与否都需要清除延迟定时器
clearInterval(timeoutTimer); //不管成功与否都需要清除超时定时器
})
if(options.timeout) {
timeoutTimer = setTimeout(() => {
error.value = "timeout"
}, options.timeout)
}
const placeholder = {type: Text, children: ""}
return () => {
if(loaded) {
return {type: InnerComp};
} else if(error.value && options.errorComponent) {
return {type: options.errorComponent , props: {error: errer.value}}
} else if(loading.value && options.loadongComponent) {
return {type: options.loadongComponent}
} else {
return placeholder;
}
}
}
}
}
3.2异步组件的加载, 直接沿用就行, return的对象本就是要求格式
const MyComponent = {
name: "MyComponent",
setup() {
return () => {}, // 如果setup返回的是函数则会覆盖render
},
.....
}
const vnode = {
type: MyComponent
}
3.3异步组件的卸载, 当异步组件加载成功后, 会卸载loading组件并渲染异步加载的组件, 需要更改unmount函数
function unmount(vnode) {
if(typeof vnode.type === Fragment) {
vnode.type.children.forEach(c => unmount(c))
return
} else if( typeof vnode.type === "object") {
unmount(vnode.component.subtree);
return;
}
let parent = vnode.el.parentNode;
if(parent) {
parent.removeChildren(vnode.el);
}
}
二、函数式组件
1、函数式组件
函数式组件本质就是一个普通的函数, 该函数的返回值是虚拟DOM
2、对比vue2
在vue.js3中使用函数式组件, 主要是因为它的简单性, 而不是因为它性能好
3、实现代码
函数式组件
function MyFuncComp(props) {
reutrn {type: "h1", children: props.title}
}
MyFuncComp.props = {
title: string,
}
更改patch支持
function patch(n1, n2, container, anchor) {
if(n1 && n1.type !== n2.type) {
unmount(n1);
n1 = null;
}
let { type } = n2;
if(typeof type === "string") {
} else if(typeof type === "object" || typeof type === "function"){
// type 是对象 有状态组件
// type 是函数 函数式组件
if(!n1) {
mountComponent(n2, container, anchor)
} else {
patchComponent()
}
} else if(type === Text){
} else if(type === Fragment) {
} else {
// 省略了其他类型的vnode
}
}
更改mountComponent支持
function mountComponent(vnode, container, anchor) {
const isFunctional = typeof vnode.type === "function";
let componentOptions = vnode.type;
if(isFunctional) {
componentOption = {
render: vnode.type;
props: vnode.type.props;
}
}
}