如何使用 FastImage 无侵入替换 React Native 的 Image

React Native 自带的 Image 在大量图片、复杂列表或需要更好缓存策略时,往往不如 react-native-fast-imageopen in new window 表现好。FastImage 基于 SDWebImage (iOS) 和 Glide (Android),提供更高效的缓存与加载。

若希望业务里继续写 import { Image } from 'react-native',而不必全局改成 import FastImage from 'react-native-fast-image',可采用 无侵入增强 React Native 组件方案 中的 Babel 别名 + react-native-proxy + Shim 方案:在 proxy 层把 Image 替换成基于 FastImage 的 ImageShim,对业务无侵入、类型也无需改动。

思路

  1. 用 Babel 的 module-resolverreact-native 指到项目内的 react-native-proxy.js
  2. proxy 从真实 react-native 做一层 Proxy,对 Image 返回我们实现的 ImageShim,其余组件照常透传。
  3. ImageShim 内部使用 FastImage 渲染,并做好与 RN Image 的 API 对齐:sourceresizeModestyle,以及静态方法 resolveAssetSourceprefetchgetSize 等。

这样所有 import { Image } from 'react-native' 在运行时会自动变成 FastImage 能力,无需改业务代码。

1. 安装依赖

本方案依赖 Babel 的 module-resolver 插件做别名解析,以及 FastImage 库:

# 用于在 babel.config.js 中配置 react-native 别名
yarn add -D babel-plugin-module-resolver

# FastImage
yarn add react-native-fast-image
# 或使用 fork,例如:yarn add @d11/react-native-fast-image

按 react-native-fast-image 官方文档完成 iOS/Android 的 link 或 Pod 安装(如需要)。

2. Babel 配置

babel.config.jsmodule-resolver 里为 react-native 配置别名,指向项目中的 proxy 文件(路径按项目结构调整;若已为其他 Shim 配过可跳过):

// babel.config.js
module.exports = {
  presets: ['module:@react-native/babel-preset'],
  plugins: [
    [
      'module-resolver',
      {
        root: ['./'],
        extensions: ['.ts', '.tsx', '.ios.js', '.android.js', '.js', '.json'],
        alias: {
          '^react-native$': './app/utils/react-native-proxy.js',
          // ... 其他 alias
        },
      },
    ],
  ],
};

3. react-native-proxy.js 中代理 Image

在 proxy 里对 Image 返回 ImageShim,其它导出仍用真实 RN:

// app/utils/react-native-proxy.js
const RN = require('../../node_modules/react-native');

module.exports = new Proxy(RN, {
  get(target, prop) {
    if (prop === 'Image') {
      if (!module.exports.__ImageShim) {
        module.exports.__ImageShim = require('./shim/ImageShim').default;
      }
      return module.exports.__ImageShim;
    }
    // View、Text 等若已有 Shim 可在此一并代理
    return target[prop];
  },
});

4. ImageShim 实现

Shim 内用 FastImage 渲染,并做两件事:

  • API 对齐:把 RN 的 resizeMode 字符串(如 'cover''contain')转成 FastImage 的 resizeMode 常量;对 sourcerequire(...) 得到的数字时,用 Image.resolveAssetSource 取宽高并落到 style,避免本地资源显示异常。
  • 静态方法透传:把 Image.resolveAssetSourceprefetchgetSizegetSizeWithHeadersabortPrefetch 等挂到 Shim 上,保证 Image.resolveAssetSource(require('./x.png')) 等用法在业务里仍然可用。

示例(以 react-native-fast-image 或兼容 API 的 fork 为例):

// app/utils/shim/ImageShim.tsx
import FastImage from 'react-native-fast-image'; // 或 @d11/react-native-fast-image
import React from 'react';
import type { ImageResizeMode } from 'react-native';

const RN = require('../../node_modules/react-native');

const ImageShim = (props: any) => {
  const { style, source, resizeMode, ...rest } = props;
  let flatStyle = RN.StyleSheet.flatten(style) || {};

  if (typeof source === 'number') {
    const { width, height } = RN.Image.resolveAssetSource(source);
    if (width && height) {
      flatStyle = { width, height, ...flatStyle };
    }
  }

  return (
    <FastImage
      {...rest}
      style={flatStyle}
      source={source}
      resizeMode={fastImageResizeMode(resizeMode)}
      tintColor={flatStyle.tintColor}
    />
  );
};

function fastImageResizeMode(mode: ImageResizeMode = 'cover') {
  switch (mode) {
    case 'contain':
      return FastImage.resizeMode.contain;
    case 'stretch':
      return FastImage.resizeMode.stretch;
    case 'center':
      return FastImage.resizeMode.center;
    case 'cover':
      return FastImage.resizeMode.cover;
    case 'repeat':
      return FastImage.resizeMode.cover;
    default:
      return FastImage.resizeMode.cover;
  }
}

ImageShim.resolveAssetSource = RN.Image.resolveAssetSource;
ImageShim.prefetch = RN.Image.prefetch;
ImageShim.getSize = RN.Image.getSize;
ImageShim.getSizeWithHeaders = RN.Image.getSizeWithHeaders;
ImageShim.abortPrefetch = RN.Image.abortPrefetch;

export default ImageShim;

要点:

  • 本地资源source={require('./x.png')} 时,用 RN.Image.resolveAssetSource(source) 拿到宽高并写入 style,避免 FastImage 下尺寸异常。
  • resizeMode:FastImage 使用自己的常量,需从 RN 的字符串映射过去;repeat 一般用 cover 替代(按需可再细化)。
  • 静态方法:保证 Image.resolveAssetSourceImage.prefetch 等在业务里仍挂在「Image」上,即当前 Shim 组件。

小结

步骤说明
Babel aliasreact-native 指向 react-native-proxy.js
proxyImage 返回 ImageShim,其余透传
ImageShim用 FastImage 渲染,对齐 source/resizeMode/style,并透传静态方法

业务侧继续 import { Image } from 'react-native' 即可,无需改成 FastImage,即可获得更好的缓存与加载表现。

上次更新: