这是你在开始使用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 提供初始值,data 是undefined 。正如我们从错误信息中知道的那样,试图在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'"的错误