用 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 保持不变。
我正在开发一个带有 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 保持不变。