Expo Router 中传递对象的最佳实践
问题描述
在开发 Expo Router 应用时,我们常需要在两个屏幕间传递复杂数据对象(如包含id
、title
、imageURL
等属性的项目数据)。如以下代码所示:
js
const getDetails = (item) => {
router.push({ pathname: `/details/${item.id}`, params: { item } });
};
常见挑战包括:
- URL 参数无法直接传递复杂对象(嵌套结构/特殊字符)
- 图片 URL 包含特殊字符导致路由解析失败
- 对象属性类型丢失(如 Date 类型转为字符串)
- 超过 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();
关键注意事项
数据类型完整性
- 全局状态管理可保留原始数据类型
- URL传参会将所有值转为字符串
性能对比
路由兼容性
jsx// SDK 49+ 使用方式 const { id } = useLocalSearchParams<{ id: string }>();
安全建议
- ❌ 避免通过URL传递敏感信息
- ✅ 使用全局状态管理安全数据
- 🔒 对缓存数据设置自动清理
架构建议
- 基础数据类型 → URL参数传递
- 小型非敏感对象 → JSON序列化
- 主流场景推荐 → 全局状态管理
- 超大对象或复杂结构 → 结合内存优化策略
遵循这些实践,您可高效安全地在 Expo Router 应用间传递对象,同时保持代码健壮性和可维护性。