用 react redux hooks 制作的递归看板。如何修复组件,使其不破坏水平滚动条?

Recursive kanban board made with react redux hooks. How can you fix the component, so that it does not break the horizontal scroll?

我正在开发一个带有 React 和 Redux 挂钩的项目。它具有递归嵌套的看板。当您将新卡片添加到一直滚动到右侧的看板列时,组件会重新呈现并将看板猛拉回 x=0,y-0 滚动。滚动的不是 window。它是看板的父元素。

有谁知道您需要如何重新构造列表组件(或代码的任何其他部分),以便您可以向列(在滚动容器中)添加卡片), 而不是破坏滚动位置?

多谢指点!

我试过在发送添加新卡片之前使用 ref 获取滚动的 div 的滚动位置,然后在发送添加卡片后立即再次设置该位置;但这没有用。

redux状态上的数据为:

listSlice.js

list.nodes:

{
    "0": {
        "text": "board-one",
        "id": 0,
        "view": "board",
        "childIds": [
            100,
            101,
            102,
            103,
            104
        ],
        "parentId": null,
        "order": 0,
        "isExpanded": true
    },
    "1": {
        "text": "board-two",
        "id": 1,
        "view": "board",
        "childIds": [
            200,
            201,
            202,
            203,
            204
        ],
        "parentId": null,
        "order": 1,
        "isExpanded": true
    },
    "2": {
        "text": "board-three",
        "id": 2,
        "view": "board",
        "childIds": [
            300,
            301,
            302,
            303,
            304
        ],
        "parentId": null,
        "order": 2,
        "isExpanded": true
    },
    "3": {
        "text": "board-four",
        "id": 3,
        "view": "board",
        "childIds": [
            400,
            401,
            402,
            403,
            404
        ],
        "parentId": null,
        "order": 3,
        "isExpanded": true
    },
    "100": {
        "text": "backlog",
        "id": 100,
        "childIds": [
            1000,
            1001
        ],
        "view": "column",
        "order": 0,
        "isExpanded": true
    },
    "101": {
        "text": "to do",
        "id": 101,
        "childIds": [
            2000,
            2001
        ],
        "view": "column",
        "order": 1,
        "isExpanded": true
    },
    "102": {
        "text": "in progress",
        "id": 102,
        "childIds": [
            3000,
            3001
        ],
        "view": "column",
        "order": 2,
        "isExpanded": true
    },
    "103": {
        "text": "blocked",
        "id": 103,
        "childIds": [
            4000,
            4001
        ],
        "view": "column",
        "order": 3,
        "isExpanded": true
    },
    "104": {
        "text": "complete",
        "id": 104,
        "childIds": [
            5000,
            5001
        ],
        "view": "column",
        "order": 4,
        "isExpanded": true
    },
    "200": {
        "text": "backlog",
        "id": 200,
        "childIds": [
            1000,
            1001
        ],
        "view": "column",
        "order": 0,
        "isExpanded": true
    },
    "201": {
        "text": "to do",
        "id": 201,
        "childIds": [
            2000,
            2001
        ],
        "view": "column",
        "order": 1,
        "isExpanded": true
    },
    "202": {
        "text": "in progress",
        "id": 202,
        "childIds": [
            3000,
            3001
        ],
        "view": "column",
        "order": 2,
        "isExpanded": true
    },
    "203": {
        "text": "blocked",
        "id": 203,
        "childIds": [
            4000,
            4001
        ],
        "view": "column",
        "order": 3,
        "isExpanded": true
    },
    "204": {
        "text": "complete",
        "id": 204,
        "childIds": [
            5000,
            5001
        ],
        "view": "column",
        "order": 4,
        "isExpanded": true
    },
    "300": {
        "text": "backlog",
        "id": 300,
        "childIds": [
            1000,
            1001
        ],
        "view": "column",
        "order": 0,
        "isExpanded": true
    },
    "301": {
        "text": "to do",
        "id": 301,
        "childIds": [
            2000,
            2001
        ],
        "view": "column",
        "order": 1,
        "isExpanded": true
    },
    "302": {
        "text": "in progress",
        "id": 302,
        "childIds": [
            3000,
            3001
        ],
        "view": "column",
        "order": 2,
        "isExpanded": true
    },
    "303": {
        "text": "blocked",
        "id": 303,
        "childIds": [
            4000,
            4001
        ],
        "view": "column",
        "order": 3,
        "isExpanded": true
    },
    "304": {
        "text": "complete",
        "id": 304,
        "childIds": [
            5000,
            5001
        ],
        "view": "column",
        "order": 4,
        "isExpanded": true
    },
    "400": {
        "text": "backlog",
        "id": 400,
        "childIds": [
            1000,
            1001
        ],
        "view": "column",
        "order": 0,
        "isExpanded": true
    },
    "401": {
        "text": "to do",
        "id": 401,
        "childIds": [
            2000,
            2001
        ],
        "view": "column",
        "order": 1,
        "isExpanded": true
    },
    "402": {
        "text": "in progress",
        "id": 402,
        "childIds": [
            3000,
            3001
        ],
        "view": "column",
        "order": 2,
        "isExpanded": true
    },
    "403": {
        "text": "blocked",
        "id": 403,
        "childIds": [
            4000,
            4001
        ],
        "view": "column",
        "order": 3,
        "isExpanded": true
    },
    "404": {
        "text": "complete",
        "id": 404,
        "childIds": [
            5000,
            5001
        ],
        "view": "column",
        "order": 4,
        "isExpanded": true
    },
    "1000": {
        "text": "card-one",
        "id": 1000,
        "childIds": [],
        "order": 0,
        "isExpanded": true,
        "view": "card"
    },
    "1001": {
        "text": "card-two",
        "id": 1001,
        "childIds": [],
        "order": 1,
        "isExpanded": true,
        "view": "card"
    },
    "2000": {
        "text": "card-one",
        "id": 2000,
        "childIds": [],
        "order": 0,
        "isExpanded": true,
        "view": "card"
    },
    "2001": {
        "text": "card-two",
        "id": 2001,
        "childIds": [],
        "order": 1,
        "isExpanded": true,
        "view": "card"
    },
    "3000": {
        "text": "card-one",
        "id": 3000,
        "childIds": [],
        "order": 0,
        "isExpanded": true,
        "view": "card"
    },
    "3001": {
        "text": "card-two",
        "id": 3001,
        "childIds": [],
        "order": 1,
        "isExpanded": true,
        "view": "card"
    },
    "4000": {
        "text": "card-one",
        "id": 4000,
        "childIds": [],
        "order": 0,
        "isExpanded": true,
        "view": "card"
    },
    "4001": {
        "text": "card-two",
        "id": 4001,
        "childIds": [],
        "order": 1,
        "isExpanded": true,
        "view": "card"
    },
    "5000": {
        "text": "card-one",
        "id": 5000,
        "childIds": [],
        "order": 0,
        "isExpanded": true,
        "view": "card"
    },
    "5001": {
        "text": "card-two",
        "id": 5001,
        "childIds": [],
        "order": 1,
        "isExpanded": true,
        "view": "card"
    }
}

listSlice.js

list.visibleIds:

[
    "0",
    "1",
    "2",
    "3"
]

组件: (为保持示例简单,列上只有一个添加按钮。滚动板时会发生错误,以在末尾看到一列;并且您尝试在末尾的其中一个列中添加一张卡片) . List.js

import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { v4 as uuid } from "uuid";
import {
  fetchNodesAsync,
  selectVisibleIds,
  selectParentId,
  addNewNodeAsync,
  selectNodes
} from "./listSlice";
import "./List.css";

export function List(props = {}) {
  let { isRoot } = props;
  const nodes = useSelector(selectNodes);
  const visibleIds = useSelector(selectVisibleIds);
  const parentId = useSelector(selectParentId);
  const dispatch = useDispatch();

  useEffect(() => {
    if (isRoot) {
      dispatch(fetchNodesAsync());
    }
  }, []);

  const renderList = (params) => {
    let { ids, parentView } = params;

    return (
  // This is the element that is scrolled when the error occurs.  It is scrolled to the left, so that you can see the last columns in the list:
      <ul className={`${parentView || "root"}-container`}>
        {ids.map((id) => {
          let node = nodes[id];
          return (
            <li key={uuid()} className={node.view}>
              <div className="header">
                {node.view === "column" && (
                  <button
                    onClick={(e) => {
                      e.preventDefault();
                      dispatch(addNewNodeAsync({ parentId: id }));
                    }}
                  >
                    + add item
                  </button>
                )}
                id: {node.id} {node.text}
              </div>
              <div className={`${node.view}-scroll`}>
                {node.isExpanded &&
                  node.childIds.length >= 1 &&
                  renderList({
                    ids: node.childIds,
                    parentId: id,
                    parentView: node.view
                  })}
              </div>
            </li>
          );
        })}
      </ul>
    );
  };

  return <ul className="List">{renderList({ ids: visibleIds, parentId })}</ul>;
}

我正在尝试实现 Dan Abramov 在这里描述的数据结构: https://github.com/reduxjs/redux/issues/1629

你不能这样做:<li key={uuid()} ... /> 因为 uuid 将在每次渲染时生成一个新的随机 ID,这将使 React 在每次状态更改时生成新的 DOM 节点,因此将丢失存储在先前节点内的滚动量。您应该改用 <li key={id} ... />,以便 ID 保持不变。