返回

React Native 图片加载失败?找不到 assets 文件夹?

Android

React Native 找不到 assets 文件夹中的图片?

最近,遇到一个怪事:React Native 项目里,src/assets 文件夹中的图片竟然加载不出来!明明路径写对了,却报错说文件不存在。这可真让人头疼。

问题重现

我把一张背景图片 bg1.jpg 放在 src/assets 文件夹,然后在 src/screens/splash/index.js 文件里这样引用:

import {ImageBackground} from 'react-native';
import React from 'react';

export default function SplashScreen() {
  return (
    <ImageBackground
      source={require('../../assets/bg1.jpg')}
      resizeMode={'cover'}
      style={{flex: 1}}>
    </ImageBackground>
  );
}

结果,程序报了这样一个错:

None of these files exist:

* bg1.jpg
* src\\assets\\bg1.jpg\\index(.native|.android.js|.native.js|.js|.android.jsx|.native.jsx|.jsx|.android.json|.native.json|.json|.android.ts|.native.ts|.ts|.android.tsx|.native.tsx|.tsx)

更奇怪的是,如果我把图片挪到其他文件夹,比如 screens,然后把引用路径改成 require('../../screens/bg1.jpg'),居然就好了!这到底是什么原因呢?

问题原因分析

几经周折,发现问题可能出在这几个方面:

  1. Metro Bundler 配置问题 : Metro Bundler 是 React Native 的默认打包工具。它可能没有正确地配置来处理 assets 文件夹。
  2. 缓存问题 : React Native 的打包工具或者模拟器、设备可能会缓存旧的资源信息,导致新的资源无法被正确加载。
  3. 文件命名/路径错误 : 尽管你说路径没错,但还是有必要再仔细检查一遍,包括大小写、文件扩展名等。
  4. React Native 项目配置变更影响 : 使用了 React Native CLI 并且重命名了 tsx 文件到 js 文件,可能产生意外影响。

解决方案

针对以上可能的原因,我总结了几个解决办法,大家可以逐一尝试。

1. 清理缓存

缓存问题是经常遇到的。不妨试试把各种缓存都清理一遍:

  • 清理 Watchman 缓存:

    watchman watch-del-all
    
  • 清理 React Native 打包缓存:

    # 对于 React Native CLI 项目
    npm start -- --reset-cache
    # 或者
    yarn start --reset-cache
    
     #或者在项目跟目录中尝试:
    rm -rf $TMPDIR/react-*
    
    
  • 清理 npm 或 yarn 缓存:

      npm cache clean --force
      # 或者
      yarn cache clean
    
  • 清理 Android Gradle 缓存:

    进入项目的android文件夹然后运行:

    ```
    ./gradlew clean
    ```
    
  • 重置模拟器/设备:

    • iOS 模拟器: 在模拟器菜单中选择 "Device" -> "Erase All Content and Settings..."。
    • Android 模拟器: 在 AVD Manager 中,选中模拟器,点击 "Wipe Data"。
    • 真机: 卸载应用,重新安装。

清理完缓存,重新运行项目,看看问题是否解决。

2. 检查 Metro Bundler 配置

有时候,Metro Bundler 默认配置可能无法满足我们的需求。我们可以通过修改 metro.config.js 文件(如果没有就创建一个)来调整配置。

在项目的根目录创建或编辑metro.config.js文件,可以添加或者更改assetExts 来解决此问题:

const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');

/**
 * Metro configuration
 * https://facebook.github.io/metro/docs/configuration
 *
 * @type {import('metro-config').MetroConfig}
 */

const config = {
  resolver: {
    assetExts: ['jpg', 'jpeg', 'png', 'gif', 'bmp','svg', /* 添加其他需要的图片格式 */],
  },
};

module.exports = mergeConfig(getDefaultConfig(__dirname), config);

上面代码,我们在 resolver.assetExts 数组中添加了常见的图片格式。
完成这一步,我们需要重启 Metro Bundler,变更才会生效。

进阶技巧
假如你的静态资源文件,并非都放在'assets'中, 还散落在多个不同的文件夹下。
我们可以增加resolver.assetExts同时,修改transformer.assetPlugins, 以保证所有资源都被包含其中。

const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
const path = require('path');

const config = {
  resolver: {
    assetExts: ['jpg', 'jpeg', 'png', 'gif', 'bmp','svg'],
    sourceExts: ['js', 'jsx', 'json', 'ts', 'tsx'], // 确保包含所有可能的源文件类型
     extraNodeModules: {
          // 在这里添加项目使用到的所有自定义模块
          'src': path.resolve(__dirname, 'src'),
          'components': path.resolve(__dirname, 'src/components'),
       },
  },
    watchFolders: [
    path.resolve(__dirname, 'src'),
        path.resolve(__dirname, 'node_modules'), // 确保监听 node_modules
    // 可以按需增加
    ],

  transformer: {
      assetPlugins: ['react-native-svg-transformer'], //如需要svg支持,且安装了对应插件
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: false,
      },
    }),
  },
};

module.exports = mergeConfig(getDefaultConfig(__dirname), config);

上面的配置, 需要保证,额外需要被解析的文件类型添加在sourceExts, 同时, 静态文件可能出现的其他位置在extraNodeModules以及watchFolders被声明。
这些文件,需要大家按需修改为自己实际项目中使用的路径.

3. 绝对路径引用

另一种方法是使用绝对路径引用图片。虽然不够优雅,但有时能解决问题。

首先,我们需要获取项目的根目录路径。可以借助 path 模块:

// 在项目的工具类或者配置文件中
const path = require('path');
const rootPath = path.resolve(__dirname, '..'); // 假设该文件在 src 目录下

export { rootPath };

然后在需要引用图片的地方:

import {ImageBackground} from 'react-native';
import React from 'react';
import { rootPath } from '../utils'; // 假设工具类文件在 src/utils.js
const path = require('path');

export default function SplashScreen() {
  const imagePath = path.join(rootPath, 'src/assets/bg1.jpg');
   //或者 imagePath =  `file://${rootPath}/src/assets/bg1.jpg`;
  return (
    <ImageBackground
      source={{ uri: imagePath }} // 使用 uri 方式
      resizeMode={'cover'}
      style={{flex: 1}}>
    </ImageBackground>
  );
}

这样,我们使用绝对路径来引用图片,绕过了相对路径可能引起的问题。但是这种方案通常被认为是比较糟糕的实践。

4. 重新审查目录和拼写

仔细检查文件路径、文件名和扩展名是否完全一致,包括大小写, 以及在导入时使用的目录分隔符是否正确(Unix-like 系统使用/,Windows 使用\,但在 React Native 通常推荐使用/)。
保证在require调用中的字符串没有空格或其他不可见字符.

5.检查是否安装 react-native-asset

react-native-asset是一个专门为React Native设计用于链接本地资源到iOS/Android的工具包。当标准做法下无法定位资源时,它有几率处理这种链接难题:
首先,我们需要安装这个工具包

npx install react-native-asset

之后,只需要一个命令,自动连接资源

npx react-native-asset

这个包有时会很方便。但是对于常规,目录明确的情况, 我们不需要使用它.

小结

遇到 React Native 图片加载问题,不要慌。可以先清理缓存试试, 然后不行再去考虑自定义 metro 的 config。当然检查基础拼写也非常关键, 最后, 记得以上调整如果都不生效,重新启动项目让各种配置、清理操作生效. 解决问题的过程,也是我们学习和成长的过程。