Vue.js设计与实现读书笔记十三 第十三章 异步组件与函数式组件

①异步组件是指:异步的加载一个组件并渲染他,是用于分隔代码,服务器下发组件等场景中
②函数式组件是指:允许通过一个函数来定义组件,函数的返回值作为渲染的内容,函数组件的特定是无状态(没有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, 等一系列的选项,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值