在React Native中缓存图片。一个有例子的教程

2,888 阅读7分钟

从社交媒体服务,到乘车应用程序,再到博客平台,图像在数据表现方面占有相当重要的地位。它们在增强用户体验方面发挥了很大的作用,对你的应用程序的用户友好性确实至关重要。

从开发者的角度来看,在React Native中加载远程图片并不是一个巨大的痛点。但是,即使在Component ,无论是类还是功能组件中加入了最好的优化,图片加载和重新渲染也会拖慢应用程序的速度,从而导致界面的滞后。

在本教程中,我们将首先向你展示如何在React Native中使用react-native-fast-image 库来缓存图片。然后,我们将通过一步步的说明和详细的例子来演示如何从头开始建立你自己的React Native图像缓存组件。

以下是我们将讨论的内容。

要继续学习,你应该熟悉React Native的基础知识--例如,JJSX、组件(类以及功能)和造型。你可以简单地复制和粘贴本指南中的代码块,但我建议通读整个教程以更好地理解。

什么是React Native中的图片缓存?

缓存是解决与从远程端点加载和重新渲染图片有关的问题的一个好方法。图像缓存本质上意味着将图像下载到应用的缓存目录(或任何其他可供应用访问的目录)中的本地存储,并在下次加载图像时从本地存储中加载。

在React Native中,有几种方法来处理图像缓存。如果你正在构建一个裸体的React Native应用,有一个很好的组件可以自动处理所有的图片缓存,而不需要编写任何额外的代码,这个组件叫做React Native FastImage。或者,如果你正在使用Expo或者正在做一个更复杂的项目,你可能会决定从头开始建立你自己的图像缓存组件。

什么是react-native-fast-image

react-native-fast-imageFastImage是一个用于加载图像的高性能React Native组件。FastImage积极地缓存了所有加载的图像。你可以添加你自己的请求授权头和预加载图像。react-native-fast-image ,甚至还支持GIF缓存。

要开始使用React Native FastImage,首先要导入FastImage 组件。

import FastImage from 'react-native-fast-image';

下面是FastImage 组件的基本实现。

<FastImage
  style={{ width: 200, height: 200 }}
  source={{uri: 'https://unsplash.it/400/400?image=1'}}
/>

下面是一个预览,看看是什么样子。

react-native-fast-image Example

使用FastImage 组件。一个实际的例子

让我们看看一个使用FastImage 组件的基本例子,其中有几个道具。

<FastImage
  style={{ width: 200, height: 200 }}
  source={{
      uri: '...image...url...',
      headers: { Authorization: 'someAuthToken' },
      priority: FastImage.priority.normal,
  }}
  resizeMode={FastImage.resizeMode.contain}
/>

正如你所看到的,这个例子几乎与基本的React Native图片组件相同,但却是类固醇。让我们把代码分解得更细一些。

  • source 包含了图片,它的头文件,以及更多的内容
  • uri 代表你要加载的图片的路径
  • headers 代表你可能需要的头文件(上面例子中的auth token)。
  • priority 标志着图像的优先级 - 例如,如果你需要先加载某个图像,你可以将优先级设置为FastImage.priority.high

React本地cache 属性

cache 是事情变得令人兴奋的地方。你可能很熟悉uriheader ,以及Image 组件的其他道具。对于FastImage ,也是一样的,只是稍有变化。cache 是你用来改变图片缓存和图片加载的行为的。

cache ,你可以使用三个属性。

  1. FastImage.cacheControl.immutableFastImage 组件的默认属性。只有当uri 被改变时,图像才会缓存或更新
  2. FastImage.cacheControl.web 使你能够配置FastImage 组件,使其像浏览器一样使用头文件和正常的缓存程序来缓存图像。
  3. FastImage.cacheControl.cacheOnly 使你能够限制FastImage 组件从已经缓存的图像中获取--也就是说,不做任何新的网络请求。

下面是一个带有cache 属性的图像的例子。

<FastImage
  style={{ width: 200, height: 200 }}
  source={{
    uri: 'https://unsplash.it/400/400?image=1',
    cache: FastImage.cacheControl.cacheOnly
  }}
/>

简单地说,如果你能维护一个只需加载一次的本地图片数据库,你就可以使用这个cache 属性,通过从设备存储中获取缓存图片来节省带宽成本。

如何从头开始建立一个图像缓存组件

FastImage对于裸露的React Native项目来说是很好的,但是如果你使用Expo或者有react-native-fast-image 不能满足的需求,你可能想自己写一个图像缓存组件。

在构建你自己的图像缓存组件之前,了解图像缓存的基本原理是非常关键的。让我们回顾一下。缓存图像就是把它存储在设备的本地存储中,这样下次就可以快速访问它,而不需要任何网络请求。

为了缓存一张图片,我们需要网络URI,或者该图片的URL,以及一个string 的标识符,以便在下一次获取它。我们需要为每个资源提供一个唯一的标识符,因为多个图片可能有相同的名字,这在区分本地缓存和有多余名字的图片时可能是个问题。

在本指南中,我假设你是使用expo构建你的应用,或者在裸露的React Native中通过unimodules使用expo-file-system

我们的组件应该接受三个基本的prop。

  1. source 代表网络图片的URI

  2. cacheKey ,用于图像的唯一标识符

  3. style 用于图像组件的样式:

    let image = {
        uri: "https://post.medicalnewstoday.com/wp-content/uploads/sites/3/2020/02/322868_1100-800x825.jpg",
        id: "MFdcdcdjvCDCcnh", //the unique id that you can store in your local db
      };
    
    <CustomFastImage
      source={{ uri: image.uri }}
      cacheKey={image.id}
      style={{ width: 200, height: 200 }}
    />
    

对于我们的自定义图像缓存组件的逻辑,我们将导入expo-file-system

import * as FileSystem from "expo-file-system";

首先,我们需要为我们的远程图像创建一个新的本地路径,使用cacheKey (唯一的ID)来检查它是否已经存在于本地缓存中,如果没有,则下载它。

我们需要初始化我们要接收的道具。

const CustomFastImage = (props) => {
  const {
    source: { uri },
    cacheKey,
    style,
  } = props;
...

以及从uri 获取图片扩展名的函数。

function getImgXtension(uri) {
  var basename = uri.split(/[\\/]/).pop();
  return /[.]/.exec(basename) ? /[^.]+$/.exec(basename) : undefined;
}

这个函数返回一个扩展名的数组。你可以只使用数组的第一项。

然后,我们将调用这个函数从组件的useEffect Hook中获取扩展名,并使用返回的扩展名来创建图片的本地缓存路径。

useEffect(() => {
    async function loadImg() {
      let imgXt = getImgXtension(uri);
      if (!imgXt || !imgXt.length) {
        Alert.alert(`Couldn't load Image!`);
        return;
      }
      const cacheFileUri = `${FileSystem.cacheDirectory}${cacheKey}.${imgXt[0]}`;
    }
    loadImg();
  }, []);

FileSystem.cacheDirectory 是缓存目录的路径。你可以根据自己的喜好来改变这个。

现在,我们需要用这样一个函数来检查这个路径上的图片是否已经存在。

async function findImageInCache(uri) {
  try {
    let info = await FileSystem.getInfoAsync(uri);
    return { ...info, err: false };
  } catch (error) {
    return {
      exists: false,
      err: true,
      msg: error,
    };
  }
}

将此添加到useEffect > loadImg()

useEffect(() => {
    async function loadImg() {
      let imgXt = getImgXtension(uri);
      if (!imgXt || !imgXt.length) {
        Alert.alert(`Couldn't load Image!`);
        return;
      }
      const cacheFileUri = `${FileSystem.cacheDirectory}${cacheKey}.${imgXt[0]}`;
      let imgXistsInCache = await findImageInCache(cacheFileUri);
    }
    loadImg();
  }, []);

现在我们需要一个函数来缓存图像到本地存储,如果它还没有被缓存,并返回想要的输出。

async function cacheImage(uri, cacheUri, callback) {
  try {
    const downloadImage = FileSystem.createDownloadResumable(
      uri,
      cacheUri,
      {},
      callback
    );
    const downloaded = await downloadImage.downloadAsync();
    return {
      cached: true,
      err: false,
      path: downloaded.uri,
    };
  } catch (error) {
    return {
      cached: false,
      err: true,
      msg: error,
    };
  }
}

我们还需要一个const ,用useState() 钩子来存储图像加载后的路径。

const [imgUri, setUri] = useState("");

为了获得更好的用户体验,你可以添加一个ActivityIndicator (或者根据你的偏好添加任何此类的加载指示器),并根据imgUri 状态的变化来实现它。

return (
    <>
      {imgUri ? (
        <Image source={{ uri: imgUri }} style={style} />
      ) : (
        <View
          style={{ ...style, alignItems: "center", justifyContent: "center" }}
        >
          <ActivityIndicator size={33} />
        </View>
      )}
  </>
);

useEffect Hook中,当图片在本地存储中被缓存或已经可用时,我们需要更新imgUri

  useEffect(() => {
    async function loadImg() {
      let imgXt = getImgXtension(uri);
      if (!imgXt || !imgXt.length) {
        Alert.alert(`Couldn't load Image!`);
        return;
      }
      const cacheFileUri = `${FileSystem.cacheDirectory}${cacheKey}.${imgXt[0]}`;
      let imgXistsInCache = await findImageInCache(cacheFileUri);
      if (imgXistsInCache.exists) {
        console.log("cached!");
        setUri(cacheFileUri);
      } else {
        let cached = await cacheImage(uri, cacheFileUri, () => {});
        if (cached.cached) {
          console.log("cached NEw!");
          setUri(cached.path);
        } else {
          Alert.alert(`Couldn't load Image!`);
        }
      }
    }
    loadImg();
  }, []);

下面是我们建立的CustomFastImage 组件的完整代码。

import React, { useEffect, useRef, useState } from "react";
import { Alert, Image, View, ActivityIndicator } from "react-native";
import * as FileSystem from "expo-file-system";
function getImgXtension(uri) {
  var basename = uri.split(/[\\/]/).pop();
  return /[.]/.exec(basename) ? /[^.]+$/.exec(basename) : undefined;
}
async function findImageInCache(uri) {
  try {
    let info = await FileSystem.getInfoAsync(uri);
    return { ...info, err: false };
  } catch (error) {
    return {
      exists: false,
      err: true,
      msg: error,
    };
  }
}
async function cacheImage(uri, cacheUri, callback) {
  try {
    const downloadImage = FileSystem.createDownloadResumable(
      uri,
      cacheUri,
      {},
      callback
    );
    const downloaded = await downloadImage.downloadAsync();
    return {
      cached: true,
      err: false,
      path: downloaded.uri,
    };
  } catch (error) {
    return {
      cached: false,
      err: true,
      msg: error,
    };
  }
}
const CustomFastImage = (props) => {
  const {
    source: { uri },
    cacheKey,
    style,
  } = props;
  const isMounted = useRef(true);
  const [imgUri, setUri] = useState("");
  useEffect(() => {
    async function loadImg() {
      let imgXt = getImgXtension(uri);
      if (!imgXt || !imgXt.length) {
        Alert.alert(`Couldn't load Image!`);
        return;
      }
      const cacheFileUri = `${FileSystem.cacheDirectory}${cacheKey}.${imgXt[0]}`;
      let imgXistsInCache = await findImageInCache(cacheFileUri);
      if (imgXistsInCache.exists) {
        console.log("cached!");
        setUri(cacheFileUri);
      } else {
        let cached = await cacheImage(uri, cacheFileUri, () => {});
        if (cached.cached) {
          console.log("cached NEw!");
          setUri(cached.path);
        } else {
          Alert.alert(`Couldn't load Image!`);
        }
      }
    }
    loadImg();
    return () => (isMounted.current = false);
  }, []);
  return (
    <>
      {imgUri ? (
        <Image source={{ uri: imgUri }} style={style} />
      ) : (
        <View
          style={{ ...style, alignItems: "center", justifyContent: "center" }}
        >
          <ActivityIndicator size={33} />
        </View>
      )}
    </>
  );
};
export default CustomFastImage;

结论

在本教程中,我们涵盖了你需要知道的关于React Native中图片缓存的一切。我们介绍了如何在React Native中使用react-native-fast-image 来缓存图片,以及如何从头开始构建你自己的图片缓存组件。

对于下一步,你可以考虑在组件中添加动画、加载指示器和其他的小工具。你还可以添加一个进度指示器,或者最好是使用FileSystem API的回调函数。

The postCaching images in React Native:有例子的教程首次出现在LogRocket博客上。