# 图片加载组件

使用hook来优雅编写一个图片加载组件。

# hook部分

import * as React from 'react';

function imgPromise(src: string): Promise<void> {
 return new Promise((resolve, reject) => {
   const i = new Image();
   i.onload = () => resolve();
   i.onerror = reject;
   i.src = src;
 });
}

function promiseFind(
 sourceList: string[],
 imgPromise: (src: string) => Promise<void>
): Promise<string> {
 let done = false;
 return new Promise((resolve, reject) => {
   const queueNext = (src: string) => {
     return imgPromise(src).then(() => {
       done = true;
       resolve(src);
     });
   };

   const firstPromise = queueNext(sourceList.shift() || '');

   sourceList
     .reduce((p, src) => {
       return p.catch(() => {
         if (!done) return queueNext(src);
         return;
       });
     }, firstPromise)
     .catch(reject);
 });
}

const removeBlankArrayElements = (a: string[]) => a.filter(x => x);

const stringToArray = (x: string | string[]) => (Array.isArray(x) ? x : [x]);

const cache: {
 [key: string]: Promise<string>;
} = {};

export interface useImageParams {
 loadImg?: (src: string) => Promise<void>;
 srcList: string | string[];
}

function useImage({
 loadImg = imgPromise,
 srcList,
}: useImageParams): { src: string | undefined; loading: boolean; error: any } {
 const [loading, setLoading] = React.useState(true);
 const [error, setError] = React.useState(null);
 const [value, setValue] = React.useState<string | undefined>(undefined);

 const sourceList = removeBlankArrayElements(stringToArray(srcList));
 const sourceKey = sourceList.join('');

 React.useEffect(() => {
   if (!cache[sourceKey]) {
     cache[sourceKey] = promiseFind(sourceList, loadImg);
   }

   cache[sourceKey]
     .then(src => {
       setLoading(false);
       setValue(src);
     })
     .catch(error => {
       setLoading(false);
       setError(error);
     });
 }, [sourceKey]);

 return { loading: loading, src: value, error: error };
}
export default useImage;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

# 图片组件

import React from 'react';
import useImage, { useImageParams } from './useImage';

export type ImgProps = Omit<
 React.DetailedHTMLProps<
   React.ImgHTMLAttributes<HTMLImageElement>,
   HTMLImageElement
 >,
 'src'
> &
 Omit<useImageParams, 'srcList'> & {
   src: useImageParams['srcList'];
   loader?: JSX.Element | null;
   unloader?: JSX.Element | null;
 };

export default function Img({
 src: srcList,
 loadImg,
 loader = null,
 unloader = null,
 ...imgProps
}: ImgProps) {
 const { src, loading, error } = useImage({
   srcList,
   loadImg,
 });

 if (src) return <img src={src} {...imgProps} />;
 if (loading) return loader;
 if (error) return unloader;

 return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 参考