问题背景
在写自己项目的时候,用到了扫码登录的场景,二维码的获取,加载等都需要 loading 进行等待,并且二维码会有过期自动刷新的问题,重新获取的时候也需要 loading。
场景不复杂,但是 loading 的状态是分为两个步骤的,一个是 http 请求过程中产生的 loading,一个是获取到二维码图片从 src 设置给 img 元素到最后 onlaod 之间产生的 loading,所以为了方便地使用 loading,还是给他封装成个小工具吧。
代码实现与分析
当前钩子需要做的就是准确告诉我图片是否在 loading 中,默认 loading 状态为 true,参数设计为一个 WatchSource 类型主要是为了方便在原图地址变化后,可以重置 loading 状态,通过一个 imageSrc 响应式对象记录需要展示的图片地址,观察它变化后,就进行正常的图片加载处理。
import type { WatchSource } from "vue";
/**
 * 获取图片加载状态
 * @param imgWatcheSource
 * @returns
 */
export function useImageLoading(imgWatcheSource: WatchSource<string>) {
  const loading = ref(true);
  const error = ref(false);
  const imageSrc = ref("");
  watch(imgWatcher, (originalSrc) => {
    imageSrc.value = originalSrc;
  }, {
    immediate: true
  });
  watch(imageSrc, (src) => {
    if (!src) {
      loading.value = true;
      error.value = false;
    };
    if (!loading.value) loading.value = true;
    error.value = false;
    const img = new Image();
    img.onload = () => {
      loading.value = false;
    };
    img.onerror = () => {
      loading.value = false;
      error.value = true;
    };
    img.src = src;
  }, {
    immediate: true
  });
  return {
    loading,
    error
  };
}
使用方式
<script lang="ts" setup>
const props = defineProps<{
  qrcode: string;
}>()
const { loading: imageLoading } = useImageLoading(() => props.qrcode);
</script>
<template>
  <Dialog
    v-model:visible="visible"
    modal
    header="登录极客角色畅享更多权益~"
  >
    <div class="text-center">
      使用微信扫码关注公众号即可登录
    </div>
    <div class="flex justify-center">
      <InnerLoading :loading="imageLoading">
        <NuxtImg
          :src="props.qrcode"
          :width="240"
        />
      </InnerLoading>
    </div>
    <div />
  </Dialog>
</template>
这样就不需要关心 http 请求过程中产生的 loading 以及图片在渲染过程中产生的 loading 了。
总结
为什么不在 useImageLoading 中创建一个 Image 元素,只更换 src 呢?因为当前钩子需要在 nuxt 与普通 csr 项目中复用,nuxt 运行时需要考虑使用环境,直接在函数中创建一个 Image 会由于环境缺乏该 API 导致报错。
文章源于个人项目【极客角色】开发过程中的记录