React Context数据传递异常:HomeScreen接收users为空问题排查
问题描述
我通过Supabase获取应用数据,并用React Context管理状态,核心代码如下:
export const DataContext = () => { const [posts, setPosts] = useState<Array<Post>>([]); const [profiles, setProfiles] = useState<Array<User>>([]); const [page, setPage] = useState<number>(1); const [perPage, setPerPage] = useState<number>(10); useEffect(() => { async function fetchData() { const { data: postsData } = await supabase .from("posts") .select("*") .range((page - 1) * perPage, page * perPage - 1); setPosts(postsData!.map((post) => new Post(post))); const postUids = postsData!.map((post) => post.uid); const { data: profilesData } = await supabase .from("profiles") .select("*") .in("uid", postUids); const profiles = profilesData!.map((userData: any) => { const userPosts = posts.filter((post) => post.uid === userData.uid); const user = new User({ ...userData, posts: userPosts }); return user; }); setProfiles((prevUsers) => [...prevUsers, ...profiles]); } fetchData(); }, [page, perPage]); const nextPage = () => { setPage(page + 1); }; const prevPage = () => { setPage(page - 1); }; return React.createContext({ posts, profiles, nextPage, prevPage }); };
之后在TabBarView中使用Context实现页面数据共享:
const { posts, profiles } = useContext(DataContext()); const Home = (props: any) => ( <View style={Theme().screen}> <HomeScreen posts={posts} users={users} /> </View> ); const Discover = (props: any) => ( <View style={Theme().screen}> <ExploreScreen posts={posts} users={profiles} /> </View> );
最初的问题是Context中每个User的posts数组始终为空,我在TabBarView中重复编写了关联逻辑临时修复:
const { posts, profiles } = useContext(DataContext()); const users: Array<User> = profiles.map((user) => { const userPosts = posts.filter((post) => post.uid === user.uid); user.posts.push(...userPosts); return user; }); const Home = (props: any) => ( <View style={Theme().screen}> <HomeScreen posts={posts} users={users} /> </View> ); const Discover = (props: any) => ( <View style={Theme().screen}> <ExploreScreen posts={posts} users={profiles} /> </View> );
现在TabBarView中能正常打印posts和users,但传递给HomeScreen的users props为空,仅posts正常。
示例数据与日志
来自DataContext的示例数据:
Posts: [{ "caption":"Caption", "date":"1669244422569", "imageUrls":[ "https://cdn.pixabay.com/photo/2020/05/04/16/05/mckenzie-river-5129717__480.jpg" ], "location":{ "latitude":150, "locationInfo":"City, State", "longitude":-150 }, "postId":"1669244407166", "uid":"daf6b8be-7cd0-4341-89d7-07879b207087" }] Users: [{ "blockedUsers":[], "displayName":"name", "photoURL":"https://cdn.pixabay.com/photo/2020/05/04/16/05/mckenzie-river-5129717__480.jpg", "uid":"daf6b8be-7cd0-4341-89d7-07879b207087", "verified":false }]
- TabBarView日志:
Posts: [Post, Post, Post] Users: [User, User, User]
- HomeScreen日志:
Posts: [Post, Post, Post] Users: []
问题原因
- Context创建逻辑错误:原代码中每次调用
DataContext组件都会创建新的Context实例,导致订阅组件无法稳定接收状态更新。 - useState异步更新导致数据关联失败:在Context的
fetchData中,调用setPosts后立即用posts.filter关联用户与帖子,但useState是异步更新,此时posts还是初始空数组,导致生成的User实例posts为空。 - 组件定义位置引发闭包陷阱:在TabBarView内部定义Home、Discover组件,这些子组件的闭包会捕获初始的空
users值,后续状态更新无法触发子组件重渲染。 - 直接修改原对象未触发重渲染:用
user.posts.push直接修改User实例的属性属于突变操作,React无法检测到引用变化,不会触发HomeScreen的更新。
解决方案
1. 修复Context的创建与数据关联逻辑
将Context定义在组件外部,并用Provider统一提供状态,同时直接使用刚获取的原始数据关联用户与帖子:
// 外部定义Context,确保实例唯一 export const DataContext = React.createContext<{ posts: Array<Post>; profiles: Array<User>; nextPage: () => void; prevPage: () => void; }>({ posts: [], profiles: [], nextPage: () => {}, prevPage: () => {}, }); // 创建Provider组件封装状态逻辑 export const DataProvider = ({ children }: { children: React.ReactNode }) => { const [posts, setPosts] = useState<Array<Post>>([]); const [profiles, setProfiles] = useState<Array<User>>([]); const [page, setPage] = useState<number>(1); const [perPage, setPerPage] = useState<number>(10); useEffect(() => { async function fetchData() { const { data: postsData } = await supabase .from("posts") .select("*") .range((page - 1) * perPage, page * perPage - 1); const formattedPosts = postsData!.map((post) => new Post(post)); setPosts(formattedPosts); const postUids = formattedPosts.map((post) => post.uid); const { data: profilesData } = await supabase .from("profiles") .select("*") .in("uid", postUids); const profiles = profilesData!.map((userData: any) => { // 直接用刚格式化的posts数据,避免异步状态更新的问题 const userPosts = formattedPosts.filter(post => post.uid === userData.uid); return new User({ ...userData, posts: userPosts }); }); // 分页加载时去重,避免重复添加同一用户 setProfiles(prev => { const existingUids = new Set(prev.map(u => u.uid)); const newUsers = profiles.filter(u => !existingUids.has(u.uid)); return [...prev, ...newUsers]; }); } fetchData(); }, [page, perPage]); const nextPage = () => setPage(page + 1); const prevPage = () => setPage(page - 1); return ( <DataContext.Provider value={{ posts, profiles, nextPage, prevPage }}> {children} </DataContext.Provider> ); };
2. 修复TabBarView的组件结构
将Home、Discover组件移到TabBarView外部,通过props传递数据,避免闭包陷阱;同时生成users时返回新的User实例,确保引用变化触发重渲染:
// 外部定义子组件,避免闭包问题 const Home = ({ posts, users }: { posts: Array<Post>, users: Array<User> }) => ( <View style={Theme().screen}> <HomeScreen posts={posts} users={users} /> </View> ); const Discover = ({ posts, users }: { posts: Array<Post>, users: Array<User> }) => ( <View style={Theme().screen}> <ExploreScreen posts={posts} users={users} /> </View> ); // TabBarView主组件 const TabBarView = () => { const { posts, profiles } = useContext(DataContext); // 生成关联后的users,返回新实例而非修改原对象 const users = profiles.map(user => { const userPosts = posts.filter(post => post.uid === user.uid); return new User({ ...user, posts: userPosts }); }); return ( <TabBar> <TabBar.Item label="Home"> <Home posts={posts} users={users} /> </TabBar.Item> <TabBar.Item label="Discover"> <Discover posts={posts} users={profiles} /> </TabBar.Item> </TabBar> ); };
内容的提问来源于stack exchange,提问作者Globe
相关产品推荐
相关产品推荐

