①异步组件是指:异步的加载一个组件并渲染他,是用于分隔代码,服务器下发组件等场景中
②函数式组件是指:允许通过一个函数来定义组件,函数的返回值作为渲染的内容,函数组件的特定是无状态(没有data),编写简单直观。
③vue3中的函数式组件和普通组件的性能区别不大,而vue2中函数式组件比不同组件性能好
13.1 异步组件要解决的问题
vue3异步组件,要解决的是如果只想异步渲染某些页面,该怎么办?具体实现的异步组件的功能有如下几点:
(1)允许用户指定加载出错时要渲染的组件
(2)允许用户指定Loading组件,以及展示该组件的延迟时间
(3)允许用户设置加载组件的超时时长
(4)组件加载失败时,为用户提供重试的能力
使用过vue3知道上面很多功能需要配合<suspense>
组件完成的,
13.2.1 封装defineAsyncComponent函数
defineAsyncComponent是一个高阶组件,它的返回值是一个包装组件,当异步组件加载不成功的时候,就返回一个站位的组件
13.2.2 超时与Error组件
异步组件是网络请求的形式加载,发送http请求,肯能会有超时,失败等等错误,所以defineAsyncComponent的参数是一个对象,
function defineAsyncComponent(options) {
if (typeof options === 'function') { //如果是函数使用默认的超时和错误加载页面
options = {
loader: options
}
}
const { loader } = options
......
options={
loader:()=>import("xx.vue"),//需要加载的组件
timeout:2000,// 超时
errorComponent:Myerror//加载错误的时候展示的组件
}
除了超时,还有网络错误,如果出现网络错误,并且有定义的错误的组件,将错误放到错误组件的props中,
13.2.3 延迟与Loading组件
这一节的问题是,当网络不好,组件加载太慢的时候,会出现卡死的情况,他也没有超时,也没有错误,只是加载慢,应该如何优化用户体验呢?于是就想到使用Loading组件,在加载异步组件的时候,直接就展示Loading,但是还有一个问题,如果网络环境很好,那么加载组件会很快,Loading刚渲染玩就卸载,会出现一闪而过的问题,为了解决这个问题,我们就需要Loading设置一个延时加载
那么现在参数就是如下:defineAsyncComponent
defineAsyncComponent({
loader:()=>new Promise(r=>{}),// 异步加载组件
timeout: 2000,// 超时错误时间
errorComponent: MyErrorComp ,// 错误时候展示的组件
delay:200,// Loading组件多少秒后才渲染
loadingComponent: // Loading组件
{
setup()
{
return ()=>{
return { type:"h1",children:"Loading"}
}
}
}
})
loading判断当前是不是可以渲染Loading组件,来展示,当Loading展示完成后,需要卸载,然后展示异步加载的组件,就需要卸载Loading组件
function unmount(vnode) {
if (vnode.type === Fragment) {
vnode.children.forEach(c => unmount(c))
return
} else if (typeof vnode.type === 'object') { // 如果要卸载的是组件
unmount(vnode.component.subTree) // 卸载组件对应的subTree(组件旧的Vnode)
return
}
const parent = vnode.el.parentNode
if (parent) {
parent.removeChild(vnode.el)
}
}
function defineAsyncComponent(options) {
if (typeof options === 'function') { // 如果option是一个异步加载函数
options = {
loader: options //依然变成选项对象
}
}
const { loader } = options
let InnerComp = null // 存储加载过来后的异步组件
let retries = 0
function load() {
return loader()
.catch((err) => {
if (options.onError) {
return new Promise((resolve, reject) => {
const retry = () => {
resolve(load())
retries++
}
options.onError(retry, reject, retries)
})
} else {
throw error
}
})
}
return { // 返回一个组件
name: 'AsyncComponentWrapper',
setup() {
const loaded = ref(false) //异步组件是否加载成功
const error = shallowRef(null) // 来存储网络出错,
const loading = ref(false) //是否在加载中
let loadingTimer = null // 记录异步组件超时的定时
if (options.delay) { // 如果设置了延时 ,只能在这个延时时间之后展示Loading组件
loadingTimer = setTimeout(() => {
loading.value = true
}, options.delay);
} else {
loading.value = true // 如果没有延时,标注为加载中,直接展示Loading组件
}
load()
.then(c => {
InnerComp = c
loaded.value = true
})
.catch((err) => {
console.log(err)
error.value = err
})
.finally(() => {
loading.value = false
clearTimeout(loadingTimer) // 不管成功还是失败,都清除定时器,会出现加载成,但仍然展示Loading组件的情况
})
let timer = null //超时的定时器
if (options.timeout) {
timer = setTimeout(() => {
const err = new Error(`Async component timed out after ${options.timeout}ms.`)
error.value = err
}, options.timeout)
}
const placeholder = { type: Text, children: '' }
return () => {
if (loaded.value) {
return { type: InnerComp }
} else if (error.value && options.errorComponent) {
return { type: options.errorComponent, props: { error: error.value } }
} else if (loading.value && options.loadingComponent) {
return { type: options.loadingComponent }
} else {
return placeholder // 站位组件
}
}
}
}
}
13.2.4 重试机制
重试是指当请求出现错误的时候,有能力重新发起加载组件的请求。
load函数接收一个onError函数,这个函数接收两个参数,一个是失败后重试retry,一个是失败后抛弃fail,
当请求失败的时候,catch会返回一个new Promise()包装两个函数,retry和fail作为onError的参数
let retries = 0 //重发的次数
function load() {
return loader() //执行异步函数
.catch((err) => { //如果失败了
if (options.onError) { // 如果有重新发送请求的函数
return new Promise((resolve, reject) => {
const retry = () => {
resolve(load()) // 重新发送请求
retries++ // 重新发送的次数
}
options.onError(retry, reject, retries)
})
} else {
throw error
}
})
}
// 执行load函数获取异步组件
load()
.then(c => {
InnerComp = c
loaded.value = true
})
.catch((err) => {
console.log(err)
error.value = err
})
.finally(() => {
loading.value = false
clearTimeout(loadingTimer) // 不管成功还是失败,都清除定时器,会出现加载成,但仍然展示Loading组件的情况
})
//onError函数
(retry, reject, retries) => {
retry()
console.log(99)
}
retries可以规定重复的次数
13.3 函数式组件
函数式组件就是一个返回Vnode的组件,例如:
function MyFuncComp(props) {
return { type: 'h1', children: props.title }
}
MyFuncComp.props = { // 组件的属性
title: String
}
函数式组件没有自身的状态Props,但他任然可以接收外部的props,定义在函数的属性上,
在patch中,设置支持vnode.type类型为函数或者对象类型
function mountComponent(vnode, container, anchor) {
const isFunctional = typeof vnode.type === 'function' //虚拟组件的类型是不是函数
let componentOptions = vnode.type
if (isFunctional) { //如果是函数,返回的就是Vnode,依然变成对象类型
componentOptions = {
render: vnode.type, // 返回虚拟DOM
props: vnode.type.props // 构建想要从父组件获得的props
}
}
.....
if (typeof type === 'object' || typeof type === 'function') {
if (!n1) { // 没有旧Vnode的情况下
mountComponent(n2, container, anchor);// 直接渲染组件
} else {
patchComponent(n1, n2, anchor)
}
}
函数式组件没有,data, setup, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated,
等一系列的选项,