vueuse-useImage学习

500 阅读1分钟

useImage

Reactive load an image in the browser, you can wait the result to display it or show a fallback. 在浏览器中响应式加载图像,您可以等待结果显示它或显示后备。

  1. promisify将加载图片转化成promise的形式
// hook/useImage/index.ts
import type { MaybeRef } from '@vueuse/shared'
import type { AsyncStateOptions } from '../useAsyncState'
import { useAsyncState } from '../useAsyncState'
export interface UseImageOptions {
    src: string,
    srcset?:string //它引用了 MDN 标志高清版本;在高分辨率设备上,它将被优先加载,取代 src 属性中的图像
    sizes?:string
}

async function loadImage(options:UseImageOptions): Promise<HTMLImageElement> {
    return new Promise((resolve,reject) => {
        const img = new Image()
        const {src, srcset, sizes} = options
        img.src = src
        if (srcset)
            img.srcset = srcset
        if (sizes)
            img.sizes = sizes
        img.onload = () => resolve(img)
        img.onerror = reject
    })
}
  1. 复用useAsyncState监听loadImage执行输出响应式数据
export const useImage = <Shallow extends true>(
    options:MaybeRef<UseImageOptions>,
    asyncStateOptions:AsyncStateOptions<Shallow> = {}
) => {
    const state = useAsyncState<HTMLImageElement | undefined>(
        ()=> loadImage(unref(options)),
        undefined,
        {
            resetOnExecute: true,
            ...asyncStateOptions
        }
    )

    // 监听options变化重新执行loadImage函数
    watch(
        ()=>unref(options),
        ()=>{
            state.execute(asyncStateOptions.delay)
        },
        {deep:true}
    )

    return state
}
  1. UseImage组件
// component.ts
import { useImage } from '../UseImage'
import type { UseImageOptions } from '../UseImage'
export interface RenderableComponent {
  /**
   * The element that the component should be rendered as
   *
   * @default 'div'
   */
  as?: Object | string
}

//  defineComponent 返回的值有一个合成类型的构造函数,用于手动渲染函数、TSX 和 IDE 工具
export const UseImage = defineComponent<UseImageOptions & RenderableComponent>({
  name: 'useImage',
  props: [
    'src',
    'srcset',
    'sizes',
    'as',
  ],
  setup(props, { slots }) {
    const data = reactive(useImage(props))

    return () => {
      if (data.isLoading && slots.loading)
        return slots.loading(data)

      else if (data.error && slots.error)
        return slots.error(data.error)

      if (slots.default)
        return slots.default(data)

      return h(props.as || 'img', props)
    }
  },
})

demo

<script setup lang="ts">
import {useImage} from '../../hook/UseImage/index.ts'
import {UseImage} from '../../hook/UseImage/UseImage.ts'

const imageOptions = $ref({ src: "https://place.dog/300/200" });
const { isLoading, error } = useImage(imageOptions, { delay: 2000 });
const change = () => {
  const time = new Date().getTime()
  imageOptions.src = `https://place.dog/300/200?t=${time}`
}
</script>

<template>
  <div>
    <div border="~ base rounded" bg-base shadow m-auto w-300px>
      <div
        v-if="isLoading"
        class="w-[300px] h-[200px] animate-pulse bg-gray-500/5 p-2"
      >
        Loading...
      </div>
      <div v-else-if="error">Failed</div>
      <img v-else :src="imageOptions.src" class="w-[300px] h-[200px]"  />

      <button m-2 p-1 bg-gray-500 border="~ base rounded" @click="change">Change</button>

      <hr m-2>
      <div class="w-[300px] h-[200px]  p-2">
        <UseImage :src="imageOptions.src"  h-200px  bg-base shadow m-auto w-300px>
          <template #loading>
            Loading..
          </template>
          <!-- <template #default>
            666
          </template> -->
          <template #error>
            Failed
          </template>
        </UseImage>
      </div>
    </div>
  </div>
</template>

总结

useImage监听options中src的变化响应式加载图像,在基础上封装组件更便捷的使用,状态更少

vueuse地址

vueuse.org/core/useima…