如何设计一个好用的 React Image 组件?(1)

对入参进行处理:

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,最终设置的valuepromiseFind找到的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 大体思路,但部分内容暂未实现(所以代码可读性要好一点)。其它特性,如:

  1. 支持 Suspense 形式调用;

  2. 默认在渲染图片前会进行 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前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后:

总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。

面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。

点击这里领取Web前端开发经典面试题

1054763)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-WcznN5Dh-1713251054764)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后:

总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。

面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。

点击这里领取Web前端开发经典面试题

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值