React Native/Expo宝可梦图鉴:是否应本地存储Poke-API数据提速?
问题分析与解决方案
你的代码速度慢的核心原因有两个:一是单次请求周期内要发起大量HTTP请求(900+宝可梦,每个需要2次额外请求,总请求量超1800次);二是每次启动都重复拉取远程数据,没有做本地缓存。本地存储是解决问题的关键,下面分场景给出具体实现方案:
一、本地存储方案选择
根据你的需求,有三种可行方案,各有优劣:
1. 预存数据到项目Assets(适合无需动态更新的场景)
如果你的图鉴只需要覆盖现有900+宝可梦,不需要后续数据更新,这是最快的方案:
- 步骤:
- 用Node.js写脚本批量爬取所有宝可梦数据,整理成
pokemons.json文件。 - 将JSON文件放入项目
assets文件夹。 - 在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[]; };
- 用Node.js写脚本批量爬取所有宝可梦数据,整理成
- 优点:启动零等待,完全不需要网络。
- 缺点:数据固定,更新必须发布App新版本。
2. AsyncStorage缓存(适合轻量需求,实现简单)
如果需要缓存数据,且功能以展示为主,不需要复杂查询,用AsyncStorage足够:
- 步骤:
- 安装依赖:
npx expo install @react-native-async-storage/async-storage - 修改获取逻辑,优先读缓存:
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的结构化存储和高效查询会更合适:
- 步骤:
- 安装依赖:
npx expo install realm - 定义数据模型:
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', }, }; } - 初始化并缓存数据:
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(); }; - 查询数据示例:
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实现稍复杂,需要定义数据模型。
二、额外优化建议
- 控制请求并发数:用
p-limit限制同时发起的请求数量,避免触发PokeAPI的限流机制,也能减少设备网络压力。 - 图片缓存:Expo可以用
expo-image组件自带的缓存功能,或者用react-native-fast-image,避免重复加载宝可梦图片。
内容的提问来源于stack exchange,提问作者Liiaam93
相关产品推荐
相关产品推荐

