2

我想要做什么

  1. 我正在尝试使用一组对象(习惯)并从每个对象中渲染一个“卡片”组件。
  2. 现在,对于每张卡片(习惯​​),您可以将该习惯“标记”为已完成,这将更新该卡片的状态。
  3. 我使用 React.memo 来防止其他卡片重新渲染。
  4. 每当用户将卡片标记为完成时,卡片标题都会更改为“已编辑”

我面临的问题

每当一张卡片被标记为完成时,标题就会改变,但只要任何其他卡片被标记为完成,第一张卡片的状态就会恢复。

我找不到其他面临类似问题的人,有人可以帮忙吗?

这是代码:

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);

注意:这在不使用 React.memo() 包装 Card 组件的情况下可以正常工作。

这是代码和框链接: https ://codesandbox.io/s/winter-water-c2592?file=/src/App.js

4

1 回答 1

3

问题是因为您的(自定义)记忆markHabitDone在某些组件中变成了陈旧的闭包。

注意你是如何传递markHabitDone给组件的。现在假设您单击其中一张卡片并将其标记为完成。由于您的自定义记忆功能,其他卡片不会被重新渲染,因此它们仍将具有markHabitDone 来自先前渲染的实例。因此,当您现在更新新卡中的项目时:

let newHabits = [...habits];

有来自以前...habits渲染。所以旧的物品基本上都是这样重新创建的。

使用自定义函数进行比较memo就像你的areCardEqual函数可能会很棘手,因为你可能会忘记比较一些道具并留下陈旧的闭包。

解决方案之一是摆脱自定义比较功能memo并考虑使用useCallbackmarkHabitDone功能。如果您还使用[]foruseCallback那么您必须重写markHabitDone函数(使用 的函数形式setState),这样它就不会habits像您在该函数的第一行中那样使用闭包habits读取中的数组useCallback)。

于 2021-03-14T12:30:13.027 回答