如何在React中缓存图片?

19

假设我有一个像这样的URL列表:

[ '/images/1', '/images/2', ... ]

我想要预获取其中的n个URL,以便在图像之间进行转换更快。现在在componentWillMount中我正在执行以下操作:

componentWillMount() {
      const { props } = this;
      const { prefetchLimit = 1, document = dummyDocument, imgNodes } = props;
      const { images } = document;
      const toPrefecth = take(prefetchLimit, images);
      const merged = zip(toPrefecth, imgNodes);

      merged.forEach(([url, node]) => {
        node.src = url;
      });
    }

使用如下方式定义imgNodes:

imgNodes: times(_ => new window.Image(), props.prefetchLimit),

其中timesziptake来自Ramda

现在当我像这样在 React 中使用这些 URL 时:

<img src={url} />

不管 URL 在何处使用,它都会根据EtagExpire标记命中浏览器缓存。我还计划在视图内部达到 n - 1 时预取下一个n张图片,以同样的方式重用 imgNodes

我的问题是:

  • 考虑到将有100多个组件使用此想法,但一次只有1个组件可见,这是一个有效的想法吗?

  • 这样做会遇到内存问题吗?我认为当组件卸载时,imgNodes会被垃圾回收。

我们正在使用redux,因此我可以将这些图像保存在 store 中,但这似乎是我处理缓存而不是利用浏览器的自然缓存。

这是一个多么糟糕的想法?


你有进展吗?我很感兴趣... - Mathieu K.
@MathieuK。我们基本上与上述描述的相同结构/接口,利用普通的HTTP缓存。 - Tim Roberts
1
那么你依赖于浏览器/HTTP的默认行为吗? - Mathieu K.
2
我依赖于我对HTML中img规范的理解方式。它似乎表明,一旦您要求浏览器获取资源,如果您再次请求并且它在您已知的图像列表中,则不必重新请求该资源。https://dev.w3.org/html5/spec-preview/the-img-element.html#list-of-available-images我希望如果我误解了这一点,那么浏览器/HTTP缓存会说不要重新获取。 - Tim Roberts
2个回答

19

你不需要在所有组件中都进行这个操作。一旦一张图片被浏览器下载,它就会被缓存起来并可以在所有组件中访问,因此你只需要在高层级的组件中进行一次即可。

我不确定你想通过缓存图片创建什么样的用户体验,但是你的代码只会启动图片下载,却不知道图片是否正在被下载、已经成功下载还是失败了。例如,如果你想在图片下载完成后(以使界面流畅)显示一个切换图片的按钮或者为组件添加类,你当前的代码可能无法实现。

你可以使用Promise来解决这个问题。

// create an utility function somewhere
const checkImage = path =>
    new Promise((resolve, reject) => {
        const img = new Image()
        img.onload = () => resolve(path)
        img.onerror = () => reject()

        img.src = path
    })

...

// then in your component
class YourComponent extends Component {

    this.state = { imagesLoaded: false }

    componentDidMount = () =>
        Promise.all(
            R.take(limit, imgUrls).map(checkImage)
        ).then(() => this.setState(() => ({ imagesLoaded: true })),
               () => console.error('could not load images'))

    render = () =>
        this.state.imagesLoaded
            ? <BeautifulComponent />
            : <Skeleton />
}

关于内存消耗 - 我不认为会发生任何糟糕的事情。浏览器通常限制并行xhr请求的数量,因此您无法创建巨大的堆使用峰值来崩溃任何东西,因为未使用的图像将被垃圾回收(但保留在浏览器缓存中)。

Redux存储是用于存储应用程序状态而不是应用程序资产的地方,但无论如何,您都无法在其中存储任何实际图像。


1
在调用新的 Promise(checkImage函数) 构造函数时,您缺少了reject参数吗? - Motoo
是的,确实 - 已修复 - dehumanizer

0

这很简单,而且运行良好:

//arraySrcs=["myImage1.png","myImage2.jpg", "myImage3.jpg", etc]

{arraySrcs.map((e) => (
    <img src={e} style={{ display: "none" }} />
  ))}

Tomerikoo,为什么表情符号、粗体字和链接被编辑了? - Ezequiel Aguilera
3
我猜这是因为使用了样式和图标来吸引注意力(而不是为答案添加价值),链接也没有为答案添加任何价值,基本上只是广告宣传与问题无关的东西。 - kca

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接