修复React中 "Cannot read property 'map' of undefined" 的错误

719 阅读3分钟

这是你在开始使用React时遇到的比较常见的错误之一。

Cannot read property 'map' of undefined

在这篇文章中,我们将学习如何解决这个问题。

为什么会发生这种情况

你试图map 的变量是undefined 。它最终可能是一个数组,但由于React的异步性,当变量是undefined ,你至少会遇到一次渲染。

让我们来看看这个例子的代码。在其中,我们从一个API中获取一些数据,并用这些数据来设置状态。

function MyComponent() {
  const [data, setData] = useState();

  useEffect(() => {
    fetch('/api/data')
      .then((res) => res.json())
      .then((data) => {
        setData(data);
      })
      .catch((err) => {
        console.log(err);
      });
  }, []);

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

这段代码可能看起来不错,但我们的数据获取需要一些时间,而React在第一次渲染你的JSX之前不会等待数据获取(或任何异步操作)的发生。这意味着,当数据被获取时,React正试图运行data.map(...)

由于我们在useState 钩子中没有为data 提供初始值,dataundefined 。正如我们从错误信息中知道的那样,试图在undefined 上调用map 是有问题的!

修复方案1:将变量默认为一个空数组

这第一个快速修复方案可能对你的用例来说已经足够了:在你等待数据获取的时候,把你的有状态变量默认为一个数组。比如说。

function MyComponent() {
  // Empty array in useState!
  const [data, setData] = useState([]);

  useEffect(() => {
    fetch('/api/data')
      .then((res) => res.json())
      .then((data) => {
        setData(data);
      })
      .catch((err) => {
        console.log(err);
      });
  }, []);

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

这样做的原因是,当你的数据获取正在发生时,React会在一个空的data 数组上调用map 方法。这很好--没有什么会被渲染,也不会有错误。一旦数据从API调用中加载,我们的data 状态将被设置,我们的列表将正确呈现。

修复方案2:显示一个加载指示器

虽然前面的修复方法很简单,但在数据加载前什么都不显示可能不是最好的用户体验。相反,我们可以选择显示一个加载指示器。我们有几种方法可以做到这一点,但其中一个比较简单的方法是添加另一个名为loading 的有状态变量。

function MyComponent() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);
    fetch('/api/data')
      .then((res) => res.json())
      .then((data) => {
        setData(data);
      })
      .catch((err) => {
        console.log(err);
      })
      .finally(() => {
        setLoading(false);
      });
  }, []);

  if (loading) {
    return <p>Data is loading...</p>;
  }

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

这是很简单和有效的当我们的数据开始获取时,我们将loading 设置为true 。当它完成获取时,我们将loading 设置为false 。注意,我们在Promise上使用finally 方法,因为无论获取成功还是失败,该方法都会运行。

说到获取失败...

我们也许应该处理我们的获取失败的情况。此外,如果我们的数据变量不是一个数组,我们可以向用户显示一个错误信息。后面这一点很重要,它可以确保我们永远不会尝试访问非数组的map ,因为它根本就不工作。

function MyComponent() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState();

  useEffect(() => {
    setLoading(true);
    fetch('/api/data')
      .then((res) => res.json())
      .then((data) => {
        setData(data);
      })
      .catch((err) => {
        setError(err);
      })
      .finally(() => {
        setLoading(false);
      });
  }, []);

  if (loading) {
    return <p>Data is loading...</p>;
  }

  if (error || !Array.isArray(data)) {
    return <p>There was an error loading your data!</p>;
  }

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

现在我们有一个相当安全的方法来处理我们的异步操作,而不会出现可怕的 "无法读取未定义的属性'map'"的错误