Vue.js设计与实现-异步组件与函数式组件

1.写在前面

异步组件,设数式其实和异步请求数据一样,计实件函只不过是现异通过异步加载的方式去加载和渲染组件。异步组件有什么作用,步组它可以用于代码分割和服务端下发组件等场景。组件函数式组件其实允许普通函数定义组件,设数式将函数返回值作为组件渲染的计实件函内容。函数式组件最大的现异特点就是无状态。

2异步组件要解决的步组问题

同步渲染:

Vue.js设计与实现-异步组件与函数式组件

import App from "App.vue";

Vue.js设计与实现-异步组件与函数式组件

createApp(App).mount("#app");

Vue.js设计与实现-异步组件与函数式组件

异步渲染:

const loader = ()=>import("App.vue");

loader().then(App=>{

createApp(App).mount("#app");

})

上面代码中,通过动态导入的组件方式实现加载组件,会返回一个Promise实例,设数式组件加载成功后会调用createApp函数完成挂载,计实件函从而实现异步渲染。现异

问题很多的步组小明就会问:上面代码中不是实现的是异步渲染整个页面吧,只需要渲染部分页面那么应该如何处理呢?组件

答案是:只需要实现异步加载部分组件。

import { shallowRef } from "vue";

import { CompA } from "CompA.vue";

export default{

components:{

CompA

},

setup(){

const asyncCompB = shallowRef(null);

import("CompB.vue").then(CompB=>asyncCompB.value=CompB);

return {

asyncCompB

}

}

}

上面代码中,CompA是同步加载的组件,CompB是异步加载的组件,亿华云通过动态组件绑定变量进行渲染的。

对于异步组件也要和异步请求数据一样处理异步存在的一些问题:

组件加载失败或加载超时,显示Error组件组件加载时,显示Loading组件或占位组件加载超时显示Loading组件组件加载失败后可以进行请求重试

3.异步组件的实现原理

封装defineAsyncComponent接口

在上一节的代码中,进行异步加载组件的使用方式并不简单,对此为了降低复杂度进行封装defineAsyncComponent接口。defineAsyncComponent函数是个高阶函数,输入输出都是组件,输出的返回值是包装后的组件。

function defineAsyncComponent(loader){

let InnerComp = null;

return {

name:"AsyncComponentWrapper",

setup(){

// 异步加载成功的标识符

const loaded = ref(false);

loader().then(comp=>{

InnerComp = comp;

// 加载成功后

loader.value = true;

});

return ()=>{

return loaded.value ? { type: InnerComp } : { type: Text, children: "" }

}

}

}

}

上面代码中,defineAsyncComponent函数会根据加载器loader的状态决定渲染内容,成功加载组件则渲染被加载的组件,否则显示占位内容。

超时处理与Error组件

异步加载组件和异步请求数据一样,会存在弱网加载时间长的情况,对此需要在组件加载时间超过指定时长后触发超时错误。

const AsyncComp = defineAsyncComponent({

loader:()=>import("CompA"),

timeout:2000,//ms

// 出错时要渲染的组件

errorComponent:MyErrorComponent

});

function defineAsyncComponent(options){

// 格式化配置项

if(typeof options === "function"){

options = {

loader: options

}

}

const { loader} = options

let InnerComp = null;

return {

name:"AsyncComponentWrapper",

setup(){

// 异步加载成功的标识符

const loaded = ref(false);

// 存储错误对象

const error = shallowRef(null);

loader().then(comp=>{

InnerComp = comp;

// 加载成功后

loader.value = true;

})// 捕获加载中的网站模板错误

.catch(err=>error.value = err);

let timer = null;

if(options.timeout){

timer = setTimeout(()=>{

const err = new Error("异步组件将在${ 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{

return placeholder;

}

}

}

}

}

在上面代码中,加载器添加catch捕获加载错误,在加载超时后创建一个新的错误对象,并将其赋值给error.value变量。在组件渲染时只要有error.value的值存在,且配置了errorComponent,就直接渲染errorComponent组件并将error.value的值作为组件的props传递。这样就可以在自定义的Error组件上,通过定义名为error的proprs接收错误对象。

延迟与Loading组件

前面知道异步组件和异步请求一样会受到网络影响,对此进行了超时和Error处理,在加载过程中可以设置Loading组件提供更好的用户体验。Loading的展示时机是什么时候,如何控制它的显隐,对此可以添加一个延迟时间,在加载超过指定时间才显示Loading组件。

const AsyncComp = defineAsyncComponent({

loader:()=>new Promise(res=>/*...*/),

delay:200,//ms

// loading要渲染的组件

loadingComponent:{

setup(){

return ()=>{

return { type:"h2",children:"Loading..." }

}

}

}

});

上面代码中,delay用于指定延迟展示Loading组件的时长,loadingComponent用于配置Loading组件。

function defineAsyncComponent(options){

// 格式化配置项

if(typeof options === "function"){

options = {

loader: options

}

}

const { loader} = options

let InnerComp = null;

return {

name:"AsyncComponentWrapper",

setup(){

// 异步加载成功的标识符

const loaded = ref(false);

// 存储错误对象

const error = shallowRef(null);

const loading = ref(false);

let loadingTimer = null;

if(options.delay){

loadingTimer = setTimeout(()=>{

loading.value = true;

}, options.delay)

}else{

loading.value = true

}

loader().then(comp=>{

InnerComp = comp;

// 加载成功后

loader.value = true;

})// 捕获加载中的亿华云计算错误

.catch(err=>error.value = err)

.finally(()=>{

loading.value = false;

clearTimeout(loadingTimer);

})

let timer = null;

if(options.timeout){

timer = setTimeout(()=>{

const err = new Error("异步组件将在${ 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;

}

}

}

}

}

在上面代码中,其实就是通过设置一个loading.value变量来标识是否正在加载中,如果制定了延迟时间则到时间后设置loading.value=true,否则直接设置。为了避免内存泄漏,无论组件是否加载成功,都需要将定时器进行清除,避免Loading组件在加载成功后也展示。

当然,在异步组件加载成功后,不仅要讲定时器进行清除,还需要对Loading组件进行卸载,对此需要修改unmount函数:

function unmount(vnode){

if(vnode.type === "string"){

vnode.children.forEach(comp=>unmount(comp));

return;

}else if(typeof vnode.type === "object"){

unmount(vnode.component.subtree);

return

}

const parent = vnode.el.parentVNode;

if(parent){

parent.removeChild(vnode.el);

}

}

重试机制

重试就是在加载出错时,可以重新发起加载组件的请求,提供开箱即用的重试机制对于使用者而言是很有必要的。对于组件加载出错的情况下,可以为使用者提供请求重试或直接抛出异常。

function defineAsyncComponent(options){

// 省略代码

// 记录重试次数

let retires = 0;

function load(){

return loader().catch(err=>{

if(options.onError){

return new Promise((resolve, reject)=>{

//重试

const retry = ()=>{

resolve(load());

retires++;

}

// 失败

const fail = ()=>{

reject(err)

}

options.onError(retry, fail, retries);

})

}else{

throw err

}

})

}

// 省略部分代码

}

4.函数式组件

函数式组件本质上返回值为虚拟DOM的普通函数,因为函数式组件的简单性,因此Vue.js3使用函数式组件。函数式组件自身没有状态,而是通过外部传递过来的props,对此需要给函数添加静态props属性。

function MyFunctionComp(props){

return { type:"h1",children:prop.name }

}

//定义props

MyFunctionComp.props = {

title: String

}

在有状态组件的基础上,只需要在挂载组件的逻辑可以复用mountComponent函数,在patch函数内部支持函数类型的vnode.type。

function patch(n1, n2, container, anchor){

if(n1 && n1.type !== n2.type){

unmount(n1);

n1 = null;

}

const { type} = n2;

if(typeof type === "string"){

//...普通元素

}else if(typeof type === Text){

//...文本节点

}else if(typeof type === Fragement){

//...片段

}else if(

typeof type === "object" || //有状态组件

typeof type === "function" //无状态组件

){

// vnode.type的值是选项对象,作为组件处理

if(!n1){

//挂载组件

mountComponent(n2, container, anchor);

}else{

//更新组件

patchComponent(n1, n2, anchor);

}

}

}

mountComponent函数代码:

function mountComponent(vnode, container, anchor){

const isFunctional = typeof vnode.type === "function";

let componentOptions = vnode.type;

if(isFunctional){

componentOptions = {

render: vnode.type,

props: vnode.type.props

}

}

}

在mountComponent函数中检查组件类型是函数式还是对象,对于函数式直接作为组件选项对象的render选项,将组件函数props作为组件的props。

5.写在最后

本文中主要介绍了异步组件要解决的几个问题,如何解决这几个问题,怎么设计与实现?在Vue.js3中提供了异步组件,与此同时介绍了异步组件的加载超时问题、异常处理和Loading、请求重试等,还讨论了函数式组件的实现逻辑。

人工智能
上一篇:4、选择一个安全的域名注册商进行域名注册
下一篇:以上的就是为大家介绍的关于域名的详解域名注册:域名注册0