对入参进行处理:
const removeBlankArrayElements = (a: string[]) => a.filter((x) => x);
const stringToArray = (x: string | string[]) => (Array.isArray(x) ? x : [x]);
function useImage({ srcList }: { srcList: string | string[] }): {
src: string | undefined,
loading: boolean,
error: any,
} {
// 获取url数组
const sourceList = removeBlankArrayElements(stringToArray(srcList));
// 获取用于缓存的键名
const sourceKey = sourceList.join(“”);
}
接下来就是重要的加载流程啦,定义promiseFind
方法,用于完成以上加载图片的逻辑。
/**
* 注意 此处将imgPromise作为参数传入,而没有直接使用imgPromise
* 主要是为了扩展性
* 后面会将imgPromise方法作为一个参数由使用者传入,使得使用者加载图片的操作空间更大
* 当然若使用者不传该参数,就是用默认的imgPromise方法
*/
function promiseFind(
sourceList: string[],
imgPromise: (src: string) => Promise
): Promise {
let done = false;
// 重新使用Promise包一层
return new Promise((resolve, reject) => {
const queueNext = (src: string) => {
return imgPromise(src).then(() => {
done = true;
// 加载成功 resolve
resolve(src);
});
};
const firstPromise = queueNext(sourceList.shift() || “”);
// 生成一条promise链[队列],每一个promise都跟着catch方法处理当前promise的失败
// 从而继续下一个promise的处理
sourceList
.reduce((p, src) => {
// 如果加载失败 继续加载
return p.catch(() => {
if (!done) return queueNext(src);
return;
});
}, firstPromise)
// 全都挂了 reject
.catch(reject);
});
}
再来改动useImage
。
const cache: {
- [key: string]: Promise;
+ [key: string]: Promise;
} = {};
function useImage({
- src,
+ srcList,
}: {
- src: string;
+ srcList: string | string[];
}): { 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));
// cache唯一键名
+ const sourceKey = sourceList.join(‘’);
React.useEffect(() => {
- if (!cache[src]) {
- cache[src] = imgPromise(src);
- }
+ if (!cache[sourceKey]) {
+ cache[sourceKey] = promiseFind(sourceList, imgPromise);
+ }
- cache[src]
- .then(() => {
+ cache[sourceKey]
+ .then((src) => {
setLoading(false);
setValue(src);
})
.catch(error => {
setLoading(false);
setError(error);
});
}, [src]);
return { isLoading: loading, src: value, error: error };
}
需要注意的一点:现在传入的图片链接可能不是单个src
,最终设置的value
为promiseFind
找到的src
,所以 cache
类型定义也有变化。
react-image-1
自定义 imgPromise
前面提到过,加载图片过程中,使用方可能会插入自己的逻辑,所以将 imgPromise
方法作为可选参数loadImg
传入,若使用者想自定义加载方法,可传入该参数。
function useImage({
+ loadImg = imgPromise,
srcList,
}: {
+ loadImg?: (src: string) => Promise;
srcList: string | string[];
}): { 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, imgPromise);
+ 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 };
}
实现 Img 组件
完成useImage
后,我们就可以基于其实现 Img
组件了。
预先定义好相关 API:
属性 说明 类型 默认值 src 图片链接 string / string[] - loader 可选,加载过程占位元素 ReactNode null unloader 可选,加载失败占位元素 ReactNode null loadImg 可选,图片加载方法,返回一个 Promise (src:string)=>Promise imgPromise 当然,除了以上 API,还有<img />
标签原生属性。编写类型声明文件如下:
export type ImgProps = Omit<
React.DetailedHTMLProps<
React.ImgHTMLAttributes,
HTMLImageElement
,
“src”
&
Omit<useImageParams, “srcList”> & {
src: useImageParams[“srcList”];
loader?: JSX.Element | null;
unloader?: JSX.Element | null;
};
实现如下:
export default ({
src: srcList,
loadImg,
loader = null,
unloader = null,
…imgProps
}: ImgProps) => {
const { src, loading, error } = useImage({
srcList,
loadImg,
});
if (src) return
if (loading) return loader;
if (error) return unloader;
return null;
};
测试效果如下:
react-image-2
结语
–
值得注意的是,本文遵循 react-image
大体思路,但部分内容暂未实现(所以代码可读性要好一点)。其它特性,如:
-
支持 Suspense 形式调用;
-
默认在渲染图片前会进行 decode,避免页面卡顿或者闪烁。
有兴趣的同学可以看看下面这些文章:
-
用于数据获取的 Suspense(试验阶段)[5]
-
错误边界(Error Boundaries)[6]
-
React:Suspense 的实现与探讨[7]
-
HTMLImageElement.decode()[8]
-
Chrome 图片解码与 Image.decode API[9]
参考资料
[1]
react-image: https://github.com/mbrevda/react-image
[2]
✨ 仓库传送门: https://github.com/worldzhao/build-your-own-react-image
[3]
react-use: https://github.com/streamich/react-use
[4]
tapable: https://github.com/webpack/tapable
[5]
用于数据获取的 Suspense(试验阶段): https://zh-hans.reactjs.org/docs/concurrent-mode-suspense.html
[6]
错误边界(Error Boundaries): https://zh-hans.reactjs.org/docs/error-boundaries.html#introducing-error-boundaries
[7]
React:Suspense 的实现与探讨: https://zhuanlan.zhihu.com/p/34210780
[8]
HTMLImageElement.decode(): https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLImageElement/decode
[9]
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
![](https://img-blog.csdnimg.cn/img_convert/dc9f9816d23e34479645fbe7941b1bc3.jpeg)
最后:
总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。
面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。
1054763)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
[外链图片转存中…(img-WcznN5Dh-1713251054764)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
![](https://img-blog.csdnimg.cn/img_convert/dc9f9816d23e34479645fbe7941b1bc3.jpeg)
最后:
总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。
面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。