You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native/Expo宝可梦图鉴:是否应本地存储Poke-API数据提速?

问题分析与解决方案

你的代码速度慢的核心原因有两个:一是单次请求周期内要发起大量HTTP请求(900+宝可梦,每个需要2次额外请求,总请求量超1800次);二是每次启动都重复拉取远程数据,没有做本地缓存。本地存储是解决问题的关键,下面分场景给出具体实现方案:


一、本地存储方案选择

根据你的需求,有三种可行方案,各有优劣:

1. 预存数据到项目Assets(适合无需动态更新的场景)

如果你的图鉴只需要覆盖现有900+宝可梦,不需要后续数据更新,这是最快的方案:

  • 步骤:
    1. 用Node.js写脚本批量爬取所有宝可梦数据,整理成pokemons.json文件。
    2. 将JSON文件放入项目assets文件夹。
    3. 在App中直接读取本地文件:
      // 方式1:直接require
      const allPokemons = require('./assets/pokemons.json') as PokemonType[];
      
      // 方式2:用Expo FileSystem读取
      import * as FileSystem from 'expo-file-system';
      const loadPokemons = async () => {
        const content = await FileSystem.readAsStringAsync(FileSystem.documentDirectory + 'assets/pokemons.json');
        return JSON.parse(content) as PokemonType[];
      };
      
  • 优点:启动零等待,完全不需要网络。
  • 缺点:数据固定,更新必须发布App新版本。

2. AsyncStorage缓存(适合轻量需求,实现简单)

如果需要缓存数据,且功能以展示为主,不需要复杂查询,用AsyncStorage足够:

  • 步骤:
    1. 安装依赖:
      npx expo install @react-native-async-storage/async-storage
      
    2. 修改获取逻辑,优先读缓存:
      import AsyncStorage from '@react-native-async-storage/async-storage';
      import pLimit from 'p-limit'; // 控制并发数,避免请求过载
      
      const CACHE_KEY = 'ALL_POKEMONS';
      const requestLimit = pLimit(5); // 一次最多发起5个请求
      
      export const getAllPokemon = async () => {
        // 先读取缓存
        const cached = await AsyncStorage.getItem(CACHE_KEY);
        if (cached) return JSON.parse(cached) as PokemonType[];
      
        // 无缓存则从API拉取
        let allData: PokemonType[] = [];
        let offset = 0;
        const total = 905;
        const batchSize = 10;
      
        while (offset < total) {
          // 获取宝可梦列表
          const listRes = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=${batchSize}&offset=${offset}`);
          const listJson = await listRes.json();
      
          // 批量获取详情,控制并发数
          const batchData = await Promise.all(
            listJson.results.map((p: any) => requestLimit(async () => {
              const detailRes = await fetch(p.url);
              const detail = await detailRes.json();
              const speciesRes = await fetch(detail.species.url);
              const species = await speciesRes.json();
      
              return {
                name: detail.name,
                id: detail.id,
                image: `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/${detail.id}.png`,
                description: species.flavor_text_entries,
                varieties: species.varieties,
                color: species.color.name,
                types: detail.types,
                abilities: detail.abilities,
              };
            }))
          );
      
          allData = [...allData, ...batchData];
          offset += batchSize;
        }
      
        // 存入缓存
        await AsyncStorage.setItem(CACHE_KEY, JSON.stringify(allData));
        return allData;
      };
      
  • 优点:实现简单,无需额外数据库,首次加载后后续启动直接读缓存。
  • 缺点:查询数据需遍历数组,复杂筛选/搜索性能较差。

3. Realm本地数据库(适合复杂查询需求)

如果你的图鉴需要搜索、按类型/颜色筛选等功能,Realm的结构化存储和高效查询会更合适:

  • 步骤:
    1. 安装依赖:
      npx expo install realm
      
    2. 定义数据模型:
      import Realm from 'realm';
      
      export class Pokemon extends Realm.Object {
        id!: number;
        name!: string;
        image!: string;
        description!: any[];
        varieties!: any[];
        color!: string;
        types!: any[];
        abilities!: any[];
      
        static schema = {
          name: 'Pokemon',
          primaryKey: 'id',
          properties: {
            id: 'int',
            name: 'string',
            image: 'string',
            description: 'list',
            varieties: 'list',
            color: 'string',
            types: 'list',
            abilities: 'list',
          },
        };
      }
      
    3. 初始化并缓存数据:
      export const initPokemonData = async () => {
        const realm = await Realm.open({ schema: [Pokemon.schema] });
        
        // 检查是否已有缓存
        if (realm.objects('Pokemon').length > 0) {
          realm.close();
          return;
        }
      
        let offset = 0;
        const total = 905;
        const batchSize = 10;
        const requestLimit = pLimit(5);
      
        while (offset < total) {
          const listRes = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=${batchSize}&offset=${offset}`);
          const listJson = await listRes.json();
      
          const batchData = await Promise.all(
            listJson.results.map((p: any) => requestLimit(async () => {
              const detailRes = await fetch(p.url);
              const detail = await detailRes.json();
              const speciesRes = await fetch(detail.species.url);
              const species = await speciesRes.json();
      
              return {
                id: detail.id,
                name: detail.name,
                image: `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/${detail.id}.png`,
                description: species.flavor_text_entries,
                varieties: species.varieties,
                color: species.color.name,
                types: detail.types,
                abilities: detail.abilities,
              };
            }))
          );
      
          // 写入Realm
          realm.write(() => {
            batchData.forEach(p => realm.create('Pokemon', p, Realm.UpdateMode.Modified));
          });
      
          offset += batchSize;
        }
      
        realm.close();
      };
      
    4. 查询数据示例:
      export const getPokemonById = async (id: number) => {
        const realm = await Realm.open({ schema: [Pokemon.schema] });
        const pokemon = realm.objects('Pokemon').filtered(`id = ${id}`)[0];
        const result = pokemon ? { ...pokemon } : null;
        realm.close();
        return result;
      };
      
      export const getPokemonsByColor = async (color: string) => {
        const realm = await Realm.open({ schema: [Pokemon.schema] });
        const pokemons = realm.objects('Pokemon').filtered(`color = "${color}"`).sorted('id');
        const result = Array.from(pokemons);
        realm.close();
        return result;
      };
      
  • 优点:查询高效,支持复杂筛选、排序,扩展性强。
  • 缺点:比AsyncStorage实现稍复杂,需要定义数据模型。

二、额外优化建议

  1. 控制请求并发数:用p-limit限制同时发起的请求数量,避免触发PokeAPI的限流机制,也能减少设备网络压力。
  2. 图片缓存:Expo可以用expo-image组件自带的缓存功能,或者用react-native-fast-image,避免重复加载宝可梦图片。

内容的提问来源于stack exchange,提问作者Liiaam93

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 03:55:31