React Native中API数据存入Context Provider的问题求助
React Native:异步API数据存入Context Provider的正确方式
问题描述
我是React Native新手,想从API抓取JSON数据并传入Context供Provider使用。在列表组件导入数据获取文件时能正常拿到数据,但在Context中导入时得到空数组,应该是Fetch请求完成前数据就被导出了。试过useEffect、async/await、Promise这些方法都没解决,附上相关代码,求正确的实现方式,让应用各部分能访问到这个状态。
原代码
Fetch文件(products.ts)
import React, { useState, useEffect } from 'react'; export type Products = { item: { id: number, title: string, description: string, price: number, discountPercentage: number, rating: number, stock: number, brand: string, category: string, thumbnail: string, images: [] } } export const getAllProducts = () => { const [data, setData] = useState([]); const url = "https://dummyjson.com/products" useEffect(() => { fetch(url) .then((resp) => resp.json()) .then((json) => setData(json.products)) .catch((error) => console.error(error)) }, []); return { data } }
Context文件(ProductContext.tsx)
import React, { createContext, useReducer } from 'react' import { getAllProducts, Products } from '../data/products' const { data } = getAllProducts() // 这里打印data是空数组 const initialState = { data } const ProductsContext = createContext({}) type State = { products: [Products['item']] }; type Actions = { type: string, payload: Products['item'], } const actions: any = { createProduct(state: State, action: Actions) { const product = action.payload product.id = Math.random() return { ...state, products: [...state.products, product] } }, updateProduct(state: State, action: Actions) { const updated = action.payload return { ...state, products: state.products.map(u => u.id === updated.id ? updated : u) } }, deleteProduct(state: State, action: Actions) { const product = action.payload return { ...state, products: state.products.filter((prod: { id: number }) => prod.id !== product.id) } } } export const ProductsProvider = (props: any) => { function reducer(state: any, action: Actions) { const fn: any = actions[action.type] return fn ? fn(state, action) : state } const [state, dispatch] = useReducer(reducer, initialState) return ( <ProductsContext.Provider value={{ state, dispatch }}> {props.children} </ProductsContext.Provider> ) } export default ProductsContext
可正常工作的列表文件
import React, { useContext } from 'react' import { View, FlatList } from 'react-native' import { getAllProducts, Products } from '../data/products' import ProductCard from '../components/ProductCard'; import ProductsContext from '../context/ProductContext'; const numColumns = 2 const isAdmin: boolean = true export default (props: any) => { const { data } = getAllProducts() // 这里能拿到完整数据 //const { state, dispatch }: any = useContext(ProductsContext) function getProductItemCard({ item: prod }: Products) { const nav = props.navigation return ( <ProductCard product={{ prod }} navigation={{ nav }} isAdmin={isAdmin} /> ) } return ( <View> <FlatList numColumns={numColumns} keyExtractor={p => `${p['id']}`} data={data} renderItem={getProductItemCard} /> </View> ) }
问题根源
你写的getAllProducts不是合法的自定义Hook(自定义Hook必须以use开头),且在普通函数里调用React Hook(useState/useEffect)违反了Hook规则。只有在组件或自定义Hook内部调用Hook时,React才会处理其生命周期逻辑:
- 列表组件里调用时,因为是在组件函数内部,Hook生效,
useEffect会执行异步请求,数据返回后更新state,组件重新渲染拿到最新数据。 - Context文件里直接调用时,Hook不生效,
useEffect根本不会执行,所以返回的是初始的空数组。
解决方案
步骤1:重构数据请求为纯异步函数
修改Fetch文件,把数据请求改成返回Promise的纯函数,分离UI状态和数据请求逻辑:
// products.ts export type Products = { id: number, title: string, description: string, price: number, discountPercentage: number, rating: number, stock: number, brand: string, category: string, thumbnail: string, images: string[] } // 纯异步函数,返回产品数组的Promise export const getAllProducts = async (): Promise<Products[]> => { const url = "https://dummyjson.com/products"; try { const resp = await fetch(url); const json = await resp.json(); return json.products; } catch (error) { console.error('获取产品数据失败:', error); return []; } };
步骤2:在Provider中处理数据加载
修改Context文件,在ProductsProvider内部通过useEffect调用异步函数,获取数据后通过dispatch更新useReducer的状态:
// ProductContext.tsx import React, { createContext, useReducer, useEffect } from 'react' import { getAllProducts, Products } from '../data/products' // 定义Context的类型,确保类型安全 const ProductsContext = createContext<{ state: { products: Products[], isLoading: boolean }, dispatch: React.Dispatch<any> }>({ state: { products: [], isLoading: true }, dispatch: () => {} }) type State = { products: Products[], isLoading: boolean }; // 定义action类型,避免字符串拼写错误 type Actions = | { type: 'SET_PRODUCTS', payload: Products[] } | { type: 'CREATE_PRODUCT', payload: Products } | { type: 'UPDATE_PRODUCT', payload: Products } | { type: 'DELETE_PRODUCT', payload: Products } // 重构reducer为switch-case形式,更清晰 const reducer = (state: State, action: Actions): State => { switch(action.type) { case 'SET_PRODUCTS': return { ...state, products: action.payload, isLoading: false }; case 'CREATE_PRODUCT': return { ...state, products: [...state.products, { ...action.payload, id: Math.random() }] }; case 'UPDATE_PRODUCT': return { ...state, products: state.products.map(prod => prod.id === action.payload.id ? action.payload : prod ) }; case 'DELETE_PRODUCT': return { ...state, products: state.products.filter(prod => prod.id !== action.payload.id) }; default: return state; } } export const ProductsProvider = (props: { children: React.ReactNode }) => { // 初始化状态,包含加载状态 const [state, dispatch] = useReducer(reducer, { products: [], isLoading: true }) // 在Provider挂载时加载数据 useEffect(() => { const fetchProducts = async () => { const products = await getAllProducts(); dispatch({ type: 'SET_PRODUCTS', payload: products }); }; fetchProducts(); }, []); return ( <ProductsContext.Provider value={{ state, dispatch }}> {props.children} </ProductsContext.Provider> ) } export default ProductsContext
步骤3:列表组件改用Context获取数据
修改列表组件,从Context中读取状态,同时处理加载状态:
import React, { useContext } from 'react' import { View, FlatList, ActivityIndicator } from 'react-native' import ProductCard from '../components/ProductCard'; import ProductsContext from '../context/ProductContext'; const numColumns = 2 const isAdmin: boolean = true export default (props: any) => { const { state } = useContext(ProductsContext) const { products, isLoading } = state // 数据加载中显示loading if (isLoading) { return <ActivityIndicator size="large" color="#0000ff" />; } function getProductItemCard({ item: prod }) { const nav = props.navigation return ( <ProductCard product={{ prod }} navigation={{ nav }} isAdmin={isAdmin} /> ) } return ( <View> <FlatList numColumns={numColumns} keyExtractor={p => `${p.id}`} data={products} renderItem={getProductItemCard} /> </View> ) }
说明
- 数据请求逻辑和UI状态完全分离,更符合React的设计理念。
- Provider负责管理全局状态和数据加载,所有子组件都能通过Context获取到最新的产品数据。
- 添加了
isLoading状态,优化用户体验,避免数据未加载完成时显示空列表。
内容的提问来源于stack exchange,提问作者Kauê Zuquetto
相关产品推荐
相关产品推荐

