Skip to content

Expo Router 中传递对象的最佳实践

问题描述

在开发 Expo Router 应用时,我们常需要在两个屏幕间传递复杂数据对象(如包含idtitleimageURL等属性的项目数据)。如以下代码所示:

js
const getDetails = (item) => {
    router.push({ pathname: `/details/${item.id}`, params: { item } });
};

常见挑战包括:

  1. URL 参数无法直接传递复杂对象(嵌套结构/特殊字符)
  2. 图片 URL 包含特殊字符导致路由解析失败
  3. 对象属性类型丢失(如 Date 类型转为字符串)
  4. 超过 URL 长度限制(约 2000 字符)

解决方案比较

以下表格总结了不同方法的适用场景:

方法适用场景优点缺点
URL 参数简单 ID/名称等基础数据实现简单无法处理复杂结构
JSON 序列化小型对象临时传递可传递复杂结构有 URL 长度限制
全局状态管理中大型对象/敏感数据无尺寸限制/类型安全需要额外状态管理

最佳实践实现

方案一:全局状态管理(推荐🔥)

这是最安全可靠的方式,尤其适合包含敏感信息或大型对象的场景。

jsx
// store.js (使用 Zustand)
import create from 'zustand';

export const useItemStore = create((set) => ({
  cachedItems: {},
  cacheItem: (item) => set(
    (state) => ({ cachedItems: { ...state.cachedItems, [item.id]: item } })
  ),
  removeItem: (id) => set(
    (state) => {
      const newItems = { ...state.cachedItems };
      delete newItems[id];
      return { cachedItems: newItems };
    }
  )
}));

列表页面传递对象:

jsx
// ListScreen.js
import { router } from 'expo-router';
import { useItemStore } from './store';

const ListItem = ({ item }) => {
  const cacheItem = useItemStore((state) => state.cacheItem);
  
  const handlePress = () => {
    cacheItem(item);  // 存入全局状态
    router.push(`/details/${item.id}`); // 只传递ID
  };
  
  return <TouchableOpacity onPress={handlePress}>{/*...*/}</TouchableOpacity>;
};

详情页面获取对象:

jsx
// [id].js
import { useLocalSearchParams } from 'expo-router';
import { useItemStore } from '../store';

export default function DetailScreen() {
  const { id } = useLocalSearchParams();
  const item = useItemStore((state) => state.cachedItems[id]);

  // 清理缓存(可选)
  useEffect(() => {
    return () => useItemStore.getState().removeItem(id);
  }, [id]);
  
  if (!item) return <Text>Loading...</Text>;
  
  return (
    <View>
      <Image source={{ uri: item.imageURL }} />
      <Text>{item.title}</Text>
    </View>
  );
}

内存管理技巧

大型应用可配合 useFocusEffect 监听屏幕切换,自动清理不再使用的缓存:

jsx
useFocusEffect(
  useCallback(() => {
    return () => {
      // 当屏幕失去焦点时清理缓存
      useItemStore.getState().removeItem(id);
    };
  }, [id])
);

方案二:JSON 序列化(小型对象)

仅适用于小型简单对象(< 1000 字符)的临时解决方案:

jsx
// 发送端
const getDetails = (item) => {
  const itemString = JSON.stringify(item);
  router.push({ 
    pathname: `/details/${item.id}`,
    params: { item: itemString } 
  });
};

// 接收端
const { id, item: itemString } = useLocalSearchParams();
const item = JSON.parse(itemString);

使用限制

  • 二进制数据无法直接序列化
  • 包含循环引用的对象会报错
  • URL 长度限制可能被突破
  • 性能影响(频繁序列化/反序列化)

方案三:URL 查询参数(仅基础值)

适合传递简单值类型数据:

jsx
// 传递 id 和名称
router.push(`/details/${item.id}?name=${encodeURIComponent(item.name)}`);

// 接收
const { id, name } = useLocalSearchParams();

关键注意事项

  1. 数据类型完整性

    • 全局状态管理可保留原始数据类型
    • URL传参会将所有值转为字符串
  2. 性能对比

  3. 路由兼容性

    jsx
    // SDK 49+ 使用方式
    const { id } = useLocalSearchParams<{ id: string }>();
  4. 安全建议

    • ❌ 避免通过URL传递敏感信息
    • ✅ 使用全局状态管理安全数据
    • 🔒 对缓存数据设置自动清理

架构建议

  1. 基础数据类型 → URL参数传递
  2. 小型非敏感对象 → JSON序列化
  3. 主流场景推荐 → 全局状态管理
  4. 超大对象或复杂结构 → 结合内存优化策略

遵循这些实践,您可高效安全地在 Expo Router 应用间传递对象,同时保持代码健壮性和可维护性。