React/Next-js:获取 TypeError:无法读取未定义的属性(读取“id”),但对象显然不为空?
将
Next-js
React
复选框列表代码段提取到沙箱后,我遇到了问题。
每当我单击复选框时,我都会收到错误:
TypeError:无法读取未定义的属性(读取“id”)
源自行
264
:
setCheckedThread(prev => new Set(prev.add(pageData.currentThreads[index].id)));
但在
index.js
的顶部,我已定义静态
JSON
并且在
useEffect()
中,我使用以下内容更新
pageData
状态:
setPageData({
currentPage: threadsDataJSON.threads.current_page,
currentThreads: threadsDataJSON.threads.data,
totalPages: totalPages,
totalThreads: threadsDataJSON.threads.total,
});
那么为什么当我单击
checkbox
时它会抛出错误?
我的沙盒链接: https://codesandbox.io/s/infallible-goldberg-vfu0ve?file=/pages/index.js
看起来您的第 280 行上的
useEffect
仅在您选中某个框后才会触发(出于某种原因),因此在触发该
useEffect
之前,
pageData.currentThreads
仍为空,这就是您遇到的错误的来源。
我建议将所有状态初始化从
useEffect
移到
useState
调用本身。例如
// Bad
const [something, setSomething] = useState(/* fake initial state */);
useEffect(() => {
setSomething(/* real initial state */)
}, []);
// Good
const [something, setSomething] = useState(/* real initial state */);
发生这种情况的原因是,在
Home
中,您创建了
handleOnChange
函数,该函数被传递给
List
组件,然后传递给记忆化的
Item
组件。如果您编写的以下函数返回 true,则
Item
组件在渲染过程中保持不变(不会重新渲染):
function itemPropsAreEqual(prevItem, nextItem) {
return (
prevItem.index === nextItem.index &&
prevItem.thread === nextItem.thread &&
prevItem.checked === nextItem.checked
);
}
这意味着
Item
组件保存了
Home
首次渲染时创建的
handleOnChange
函数的第一个初始版本。此版本的
hanldeOnChange
仅知道
pageData
的初始状态,因为它对初始
pageData
状态有一个闭包,而这不是最新的状态值。您可以不记忆
Item
组件,也可以更改
itemPropsAreEqual
,以便在
props.handleOnChange
更改时重新渲染
Item
:
function itemPropsAreEqual(prevItem, nextItem) {
return (
prevItem.index === nextItem.index &&
prevItem.thread === nextItem.thread &&
prevItem.checked === nextItem.checked &&
prevItem.handleOnChange === nextItem.handleOnChange // also rerender if `handleOnChange` changes.
);
}
此时,您正在检查比较函数中传递给
Item
的每个 prop,因此您不再需要它,只需使用
React.memo(Item)
即可。但是,单独更改
itemPropsAreEqual
或从
React.memo()
调用中删除
itemPropsAreEqual
现在违背了记忆
Item
组件的目的,因为
handleOnChange
每次
Home
重新渲染时都会重新创建(即被调用)。这意味着使用新比较函数进行的上述检查将始终返回
false
,导致
Item
在每次父
Home
组件重新渲染时重新渲染。要管理该问题,您可以在
Home
组件中记忆
handleOnChange
,方法是将其包装在
useCallback()
钩子中,并传递它使用的依赖项:
const handleOnChange = useCallback(
(iindex, id) => {
... your code in handleOnChange function ...
}
, [checkedState, pageData]); // dependencies for when a "new" version of `handleOnChange` should be created
这样,仅在需要时创建新的
handleOnChange
引用,导致您的
Item
组件重新渲染以使用新的最新的
handleOnChange
函数。还有
useEvent()
钩子,这是一个实验性的 API 功能,您可以考虑使用它来代替
useCallback()
(这样
Item
就不需要重新渲染来处理
handleOnChange
),但在撰写本文时尚未可用(不过,您可以通过
创建垫片或使用替代解决方案
暂时将其用作自定义钩子)。
请参阅 此处 的工作示例。