😎

【React Native】Expo ImageはFastImageの代替となり得るか

2023/04/10に公開

はじめに

こんにちは!
犬専用の音楽アプリ オトとりっぷでエンジニアしています、足立です!

https://www.oto-trip.com/

この記事では、Expo48から登場したExpo ImageのiOSでのパフォーマンスを測定し、FastImageの代替となり得るかを調査しようと思います。
結果だけ知りたい!という方は、パフォーマンス測定まで読み飛ばしください。

目次

  • 導入方法
    • FastImage
    • Expo Image
  • パフォーマンス測定
    • 表示までの速度
    • フレームレート
    • 結論

導入方法

まずは、いつものセットアップから始めます。

$ npx react-native@latest init AwesomeProject
$ cd AwesomeProject

これで初期プロジェクトの作成が完了しました。
次にApp.tsxを以下の通りに書き換えます。

App.tsx
import React, {useCallback, useMemo} from 'react';
import {FlatList, Image, SafeAreaView, StyleSheet} from 'react-native';

const IMAGE_SIZE = 100;
const getImageUrl = (index: number) => {
  return `https://picsum.photos/id/${index}/${IMAGE_SIZE}/${IMAGE_SIZE}`;
};

const App = () => {
  const array = useMemo(() => new Array(1000), []);

  const getComponent = useCallback(({index}: {index: number}) => {
    return <Image style={styles.image} source={{uri: getImageUrl(index)}} />;
  }, []);

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={array}
        numColumns={3}
        renderItem={getComponent}
        initialNumToRender={20}
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
  },
  image: {
    height: IMAGE_SIZE,
    width: IMAGE_SIZE,
    margin: 8,
  },
});

export default App;

これで、画像がいっぱい表示されるページを作成できました。

こちらImageコンポーネントにはReact Nativeオリジナル(RN Image)を使用しているので、次にFastImageとExpo Image追加してこうと思います。


FastImage

公式のインストール方法は以下の通りです。

https://github.com/DylanVann/react-native-fast-image#usage

$ yarn add react-native-fast-image
$ cd ios && pod install

めちゃくちゃ簡単ですね。
では早速追加して、App.tsxを以下の通りに書き換えます。
(重複部については一部省略しています)

App.tsx
import React, {useCallback, useMemo} from 'react';
import {FlatList, SafeAreaView, StyleSheet} from 'react-native';
+ import FastImage from 'react-native-fast-image';

const App = () => {
  const array = useMemo(() => new Array(1000), []);

  const getComponent = useCallback(({index}: {index: number}) => {
-    return <Image style={styles.image} source={{uri: getImageUrl(index)}} />;
+    return (
+      <FastImage style={styles.image} source={{uri: getImageUrl(index)}} />
+    );
  }, []);

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={array}
        numColumns={3}
        renderItem={getComponent}
        initialNumToRender={20}
      />
    </SafeAreaView>
  );
};

Expo Image

次に本丸のExpo Imageを導入していきます。
ところが、このままExpo Imageを追加することはできません。
以下のようにSDWebImageWebPCoderのバージョン違いによりnpx pod-installに失敗します。

[!] CocoaPods could not find compatible versions for pod "SDWebImageWebPCoder":
  In Podfile:
    ExpoImage (from `../node_modules/expo-image/ios`) was resolved to 1.0.0, which depends on
      SDWebImageWebPCoder (~> 0.9.1)

    RNFastImage (from `../node_modules/react-native-fast-image`) was resolved to 8.6.3, which depends on
      SDWebImageWebPCoder (~> 0.8.4)

しょうがないので、FastImageさんには一旦退場していただいてから、Expo関連ライブラリを追加します。

$ yarn remove react-native-fast-image
$ yarn add expo expo-modules-core expo-image

次に、Expo導入のために各種設定ファイルを修正します。
ExpoライブラリをReact Native bearで使用する場合(npx react-native@latest initでプロジェクトを開始した場合)以下の通り各種設定ファイルを修正する必要があります。

https://docs.expo.dev/bare/installing-expo-modules/

と、言ってもコマンド一発で設定ファイルを変更してくれます。
手動で変更することも可能ですが、今回はコマンド一発の自動にお任せしましょう。

$ npx install-expo-modules@latest

最後にApp.tsxを以下の通りに書き換えます。

App.tsx
import React, {useCallback, useMemo} from 'react';
import {FlatList, SafeAreaView, StyleSheet} from 'react-native';
- import FastImage from 'react-native-fast-image';
+ import {Image as ExpoImage} from 'expo-image';

const App = () => {
  const array = useMemo(() => new Array(1000), []);

  const getComponent = useCallback(({index}: {index: number}) => {
    return (
-      <FastImage style={styles.image} source={{uri: getImageUrl(index)}} />
+      <ExpoImage
+        style={styles.image}
+        source={getImageUrl(index)}
+        cachePolicy={'memory-disk'}
+      />
    );
  }, []);

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={array}
        numColumns={3}
        renderItem={getComponent}
        initialNumToRender={20}
      />
    </SafeAreaView>
  );
};

cachePolicyは、以下の通りキャッシュ場所を設定することができます。

cachePolicy 説明
none キャッシュしません
disk diskにキャッシュされます(デフォルト値)
memory memoryにキャッシュされます
memory-disk memoryにキャッシュされますが、diskにもフォールバックされます

https://docs.expo.dev/versions/unversioned/sdk/image/#cachepolicy

なお今回は、デフォルト値のdiskと最もキャッシュされるmemory-diskを測定に採用しました。

パフォーマンス測定

表示までの速度

ではそれぞれを見比べてみましょう。
画像を表示完了させてからリロードし、再度表示される様子を確認します。

RN Image FastImage Expo Image (memory-disk) Expo Image (default)

RN Imageはやはり遅いですね。
FastImageとExpo Image (memory-disk)はほぼ同時ですが、若干FastImageの方が初期表示までが早い気がします。
Expo Image (default)はdiskから読み込んでいるためか、Expo Image (memory-disk)よりも表示されるまでのラグが大きいようです。

フレームレート

次にフレームレートの計測をしていきます。
RN Imageはお話にならないぐらい遅いので、FastImageとExpo Imageの比較です。

FastImage Expo Image (memory-disk)

JSフレームレートを確認します。(画面左上のメトリクス一番右側の数字)
スクロール動作によってJSフレームレートが悪化しており、FastImageは瞬間的に15まで低下、Expo Imageは20台まで低下しています。
とはいえ、FastImageとExpo Imageの間に劇的な差はなさそうです。

結論

  • 初期表示速度は、FastImageがExpo Imageよりも若干早かった。
  • フレームレートは、Expo ImageがFastImageよりも若干高かった。

ということで、「パフォーマンス面において、代替可能であるがすぐに置き換えを検討すべき優位性は見出せない」と言えそうです。
もちろん、Expo Imageにはblurhashなどのパフォーマンス面以外の優れた機能を備えていますので、そちらに優位性があればすぐにでも置き換えを検討すべきかもしれません。

最後に

ここまで読んでいただきありがとうございました。
ライブラリの選択肢が増えることは非常にありがたいですね!
オトとりっぷでも要件次第では導入を検討したいと思います。

もし犬専用の音楽アプリに興味を持っていただけたら、ぜひダウンロードしてみてください!

https://www.oto-trip.com/

Discussion