You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 19:02:28