React Context 和 JavaScript Promises 的意外行为

问题描述 投票:0回答:2

我正在开发一个 React 应用程序,其中使用 React 的 Context API 来管理全局状态。我有一个上下文提供程序,它使用 JavaScript 的本机

fetch
函数从 API 获取数据,该函数返回 Promise。

这是我的代码的简化版本:

import React, { createContext, useState, useEffect } from 'react';

export const MyContext = createContext();

export const MyProvider = props => {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('https://myapi.com/data')
      .then(response => response.json())
      .then(data => setData(data));
  }, []);

  return (
    <MyContext.Provider value={{ data }}>
      {props.children}
    </MyContext.Provider>
  );
};

我在子组件中使用此上下文,如下所示:

import React, { useContext } from 'react';
import { MyContext } from './MyProvider';

const MyComponent = () => {
  const { data } = useContext(MyContext);

  return (
    <div>
      {data ? data.map(item => <div key={item.id}>{item.name}</div>) : 'Loading...'}
    </div>
  );
};

我面临的问题是,有时子组件会在获取数据之前渲染,导致它抛出错误,因为它试图映射 null。然而,其他时候,它工作得很好。

我认为带有空依赖项数组的 useEffect 将确保获取操作在其他任何内容渲染之前完成,但情况似乎并非如此。

任何人都可以解释为什么会发生这种情况以及如何确保获取操作始终在子组件尝试访问数据之前完成?

提前谢谢您!

javascript reactjs react-hooks fetch-api
2个回答
0
投票

我认为使用空依赖数组的 useEffect 将确保获取操作在其他任何内容渲染之前完成,但情况似乎并非如此。

事实并非如此。 (您需要

<Suspense>
或类似工具。)效果不会也不能阻止渲染。

如何确保获取操作始终在子组件尝试访问数据之前完成?

在获得数据之前不要渲染子组件。

if(data === null) return <>Loading...</>;
return (
 <MyContext.Provider value={{ data }}>
   {props.children}
 </MyContext.Provider>
);

如果您使用 TypeScript 并且有一个正确键入的上下文(其中

data
不能是
null
),TypeScript 会防止您出现此错误。


0
投票

您正在处理

fetch
函数的异步性质。它返回一个 Promise,该 Promise 在从 API 获取数据时完成。 JavaScript 不会暂停以完成此 Promise,因此您的组件可能会在数据获取之前呈现,从而在尝试映射时导致错误
null

解决方案:向上下文提供程序添加加载状态。

import React, { createContext, useState, useEffect } from 'react';

export const MyContext = createContext();

export const MyProvider = props => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('https://myapi.com/data')
      .then(response => response.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, []);

  return (
    <MyContext.Provider value={{ data, loading }}>
      {props.children}
    </MyContext.Provider>
  );
};

在您的子组件中,在映射之前检查数据是否仍在加载:

import React, { useContext } from 'react';
import { MyContext } from './MyProvider';

const MyComponent = () => {
  const { data, loading } = useContext(MyContext);

  if (loading) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      {data.map(item => <div key={item.id}>{item.name}</div>)}
    </div>
  );
};

这样,您的组件最初会显示“正在加载...”,并且在从 API 获取数据之前不会映射数据。

© www.soinside.com 2019 - 2024. All rights reserved.