React Memo 重置组件状态中的值
React Memo resets values in component state
我想做什么
- 我正在尝试使用一组 objects(习惯)并从每个习惯中渲染一个“卡片”组件。
- 现在,对于每张卡片(习惯),您都可以将该习惯“标记”为已完成,这将更新该卡片的状态。
- 我已经使用 React.memo 来防止其他卡 re-rendering。
- 只要用户将卡片标记为完成,卡片 header 就会更改为“已编辑”
我面临的问题
每当一张卡片被标记为完成时,header 会正常改变,但是每当任何 other 卡片被标记为完成时,第一张卡片的状态就会恢复。
我找不到其他面临类似问题的人,有人可以帮忙吗?
代码如下:
import React, { useState } from "react";
const initialState = {
habits: [
{
id: "1615649099565",
name: "Reading",
description: "",
startDate: "2021-03-13",
doneTasksOn: ["2021-03-13"]
},
{
id: "1615649107911",
name: "Workout",
description: "",
startDate: "2021-03-13",
doneTasksOn: ["2021-03-14"]
},
{
id: "1615649401885",
name: "Swimming",
description: "",
startDate: "2021-03-13",
doneTasksOn: []
},
{
id: "1615702630514",
name: "Arts",
description: "",
startDate: "2021-03-14",
doneTasksOn: ["2021-03-14"]
}
]
};
export default function App() {
const [habits, setHabits] = useState(initialState.habits);
const markHabitDone = (id) => {
let newHabits = [...habits];
let habitToEditIdx = undefined;
for (let i = 0; i < newHabits.length; i++) {
if (newHabits[i].id === id) {
habitToEditIdx = i;
break;
}
}
let habit = { ...habits[habitToEditIdx], doneTasksOn: [], name: "Edited" };
newHabits[habitToEditIdx] = habit;
setHabits(newHabits);
};
return (
<div className="App">
<section className="test-habit-cards-container">
{habits.map((habit) => {
return (
<MemoizedCard
markHabitDone={markHabitDone}
key={habit.id}
{...habit}
/>
);
})}
</section>
</div>
);
}
const Card = ({
id,
name,
description,
startDate,
doneTasksOn,
markHabitDone
}) => {
console.log(`Rendering ${name}`);
return (
<section className="test-card">
<h2>{name}</h2>
<small>{description}</small>
<h3>{startDate}</h3>
<small>{doneTasksOn}</small>
<div>
<button onClick={() => markHabitDone(id, name)}>Mark Done</button>
</div>
</section>
);
};
const areCardEqual = (prevProps, nextProps) => {
const matched =
prevProps.id === nextProps.id &&
prevProps.doneTasksOn === nextProps.doneTasksOn;
return matched;
};
const MemoizedCard = React.memo(Card, areCardEqual);
注意:无需在 Card 组件上使用 React.memo() 包装即可正常工作。
这里是codesandbox link: https://codesandbox.io/s/winter-water-c2592?file=/src/App.js
问题是因为您的(自定义)记忆 markHabitDone
在某些组件中变成了陈旧的闭包。
注意如何将 markHabitDone
传递给组件。现在假设您单击其中一张卡片并将其标记为完成。由于您的自定义记忆功能,其他卡片将不会被重新渲染,因此它们仍然会有一个 markHabitDone
来自先前渲染的实例.因此,当您现在更新新卡片中的项目时:
let newHabits = [...habits];
...habits
来自之前的渲染。所以旧项目基本上都是这样重新创建的。
在 memo
中使用自定义函数进行比较,就像您的 areCardEqual
函数一样,可能会很棘手,因为您可能会忘记比较某些道具并留下陈旧的闭包。
其中一个解决方案是取消 memo
中的自定义比较函数,并考虑将 useCallback
用于 markHabitDone
函数。如果您还对 useCallback
使用 []
,那么您必须重写 markHabitDone
函数(使用 setState
的 functional 形式),这样它就不会读取habits
使用闭包,就像您在该函数的第一行中那样(否则它将始终读取 habits
的旧值,因为 useCallback
中的数组为空)。
我想做什么
- 我正在尝试使用一组 objects(习惯)并从每个习惯中渲染一个“卡片”组件。
- 现在,对于每张卡片(习惯),您都可以将该习惯“标记”为已完成,这将更新该卡片的状态。
- 我已经使用 React.memo 来防止其他卡 re-rendering。
- 只要用户将卡片标记为完成,卡片 header 就会更改为“已编辑”
我面临的问题
每当一张卡片被标记为完成时,header 会正常改变,但是每当任何 other 卡片被标记为完成时,第一张卡片的状态就会恢复。
我找不到其他面临类似问题的人,有人可以帮忙吗?
代码如下:
import React, { useState } from "react";
const initialState = {
habits: [
{
id: "1615649099565",
name: "Reading",
description: "",
startDate: "2021-03-13",
doneTasksOn: ["2021-03-13"]
},
{
id: "1615649107911",
name: "Workout",
description: "",
startDate: "2021-03-13",
doneTasksOn: ["2021-03-14"]
},
{
id: "1615649401885",
name: "Swimming",
description: "",
startDate: "2021-03-13",
doneTasksOn: []
},
{
id: "1615702630514",
name: "Arts",
description: "",
startDate: "2021-03-14",
doneTasksOn: ["2021-03-14"]
}
]
};
export default function App() {
const [habits, setHabits] = useState(initialState.habits);
const markHabitDone = (id) => {
let newHabits = [...habits];
let habitToEditIdx = undefined;
for (let i = 0; i < newHabits.length; i++) {
if (newHabits[i].id === id) {
habitToEditIdx = i;
break;
}
}
let habit = { ...habits[habitToEditIdx], doneTasksOn: [], name: "Edited" };
newHabits[habitToEditIdx] = habit;
setHabits(newHabits);
};
return (
<div className="App">
<section className="test-habit-cards-container">
{habits.map((habit) => {
return (
<MemoizedCard
markHabitDone={markHabitDone}
key={habit.id}
{...habit}
/>
);
})}
</section>
</div>
);
}
const Card = ({
id,
name,
description,
startDate,
doneTasksOn,
markHabitDone
}) => {
console.log(`Rendering ${name}`);
return (
<section className="test-card">
<h2>{name}</h2>
<small>{description}</small>
<h3>{startDate}</h3>
<small>{doneTasksOn}</small>
<div>
<button onClick={() => markHabitDone(id, name)}>Mark Done</button>
</div>
</section>
);
};
const areCardEqual = (prevProps, nextProps) => {
const matched =
prevProps.id === nextProps.id &&
prevProps.doneTasksOn === nextProps.doneTasksOn;
return matched;
};
const MemoizedCard = React.memo(Card, areCardEqual);
注意:无需在 Card 组件上使用 React.memo() 包装即可正常工作。
这里是codesandbox link: https://codesandbox.io/s/winter-water-c2592?file=/src/App.js
问题是因为您的(自定义)记忆 markHabitDone
在某些组件中变成了陈旧的闭包。
注意如何将 markHabitDone
传递给组件。现在假设您单击其中一张卡片并将其标记为完成。由于您的自定义记忆功能,其他卡片将不会被重新渲染,因此它们仍然会有一个 markHabitDone
来自先前渲染的实例.因此,当您现在更新新卡片中的项目时:
let newHabits = [...habits];
...habits
来自之前的渲染。所以旧项目基本上都是这样重新创建的。
在 memo
中使用自定义函数进行比较,就像您的 areCardEqual
函数一样,可能会很棘手,因为您可能会忘记比较某些道具并留下陈旧的闭包。
其中一个解决方案是取消 memo
中的自定义比较函数,并考虑将 useCallback
用于 markHabitDone
函数。如果您还对 useCallback
使用 []
,那么您必须重写 markHabitDone
函数(使用 setState
的 functional 形式),这样它就不会读取habits
使用闭包,就像您在该函数的第一行中那样(否则它将始终读取 habits
的旧值,因为 useCallback
中的数组为空)。