从 ReactJS 中的 dom 中删除动态呈现的元素

Remove dynamic rendered element from dom in ReactJS

目前我有一个看起来像这样的反应组件:

const GeraCard = (cards, cart = false) => {
    return cards.map((v, i) => {
      return (
        <div key={i} className={styles.card}>
          <div onClick={() => urlRender(v.url)} className={styles.cardContent}>
            <div>
              <span className={styles.cardTitulo}>{v.Nome}</span>
            </div>
            <div>
              <span className={styles.cardData}>{v.Data}</span>
              <span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span>
            </div>
            {cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null}
          </div>
          <span className={styles.trash}>
            <FontAwesomeIcon
              icon={faTrash}
              color={"#3c3c3c77"}
              onClick={(e) => {
                e.persist()
                TrashHandler(v.Nome, e)
              }}
            />
          </span>
        </div>
      );
    });
  };

基于卡片数组,它呈现如下内容:

Rendered Component

每当我单击垃圾桶按钮时,我都会向后端发出请求,编辑数据库中的列表并根据现在更新的“卡片”重新呈现组件。问题是这需要一些时间才能发生,所以我想要一种方法在我的后端完成它的工作时立即将它从 dom 中删除。

有点像

{show ? renderCompoennt : null}

我试过使用 vanilla javascript 从垃圾桶中抓取父级,这将是我要删除的卡,但结果不可预测,而且速度也很慢。

我最近的尝试是这样的:

const GeraCard = (cards, cart = false) => {
    return cards.map((v, i) => {
      const [show, setShow] = useState(true);
      return (
        <div key={i}>
          {show ?
            <div className={styles.card}>
              <div onClick={() => urlRender(v.url)} className={styles.cardContent}>
                <div>
                  <span className={styles.cardTitulo}>{v.Nome}</span>
                </div>
                <div>
                  <span className={styles.cardData}>{v.Data}</span>
                  <span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span>
                </div>
                {cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null}
              </div>
              <span className={styles.trash}>
                <FontAwesomeIcon
                  icon={faTrash}
                  color={"#3c3c3c77"}
                  onClick={(e) => {
                    setShow(false);
                    e.persist()
                    TrashHandler(v.Nome, e)
                  }}
                />
              </span>
            </div> :
            null
          }
        </div>
      );
    });
  };

但是 React 不允许我这样做。尽管速度很快,但每次删除一项时,React 都会抱怨“渲染的挂钩较少”并导致应用程序崩溃。

问题是在第一个渲染中你有 {cards.length} 调用来钩住 GeraCard 中的“useState”,但是在删除一张卡片后,你将有 {cards.length-1}调用挂钩“useState”。正如 React 文档所述:

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.

您应该将地图回调的内容提取到单独的组件中。

const GeraCards = (cards, cart = false) => {
    return cards.map((v, i) =>
        <GeraCard card={v} index={i} cart={cart} />
    );
};

const GeraCard = ({ card, index, cart }) => {
    const [show, setShow] = useState(true);
    const v = card;
    return (
        <div key={index}>
            {show ?
                <div className={styles.card}>
                    <div onClick={() => urlRender(v.url)} className={styles.cardContent}>
                        <div>
                            <span className={styles.cardTitulo}>{v.Nome}</span>
                        </div>
                        <div>
                            <span className={styles.cardData}>{v.Data}</span>
                            <span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span>
                        </div>
                        {cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null}
                    </div>
                    <span className={styles.trash}>
                        <FontAwesomeIcon
                            icon={faTrash}
                            color={"#3c3c3c77"}
                            onClick={(e) => {
                                setShow(false);
                                e.persist()
                                TrashHandler(v.Nome, e)
                            }}
                        />
                    </span>
                </div> :
                null
            }
        </div>
    );
}

您正在尝试做一些 Optimistic UI,其中您假设您的操作会成功,并在对后端的请求完成之前立即反映 expected/assumed 状态。这将代替显示一些 progress/busy 指示器,例如微调器,直到服务器完成操作。

您的代码中的第一个问题和紧迫的问题 -- 它违反了 rules of hooks,其中规定挂钩只能在顶层使用(绝不能在循环、条件等内部)。

第二个问题是您利用 vanilla JS 直接操作 DOM;这通常是 MV* 框架中的反模式,在这里也是如此。相反,我建议在您的数据模型中进行管理;像这样:

  1. 如果卡片有 deleted 属性.
  2. ,请将您的 .map 处理程序重写为 return null
  3. 当用户点击垃圾桶按钮时,做两件事:
    1. 请求后台删除
    2. 使用 setState 添加 deleted: true 属性 到点击的卡片

现在您将获得一个重新呈现,它将忽略已删除的卡片,并向后端发出请求,所有这些都在 React 数据模型中。确保您处理复杂性:

  1. 如何处理响应
  2. 后台删除失败如何处理
  3. 如果用户在任何请求完成之前快速单击许多卡片进行删除,如何进行管理。