【发布时间】:2020-06-16 12:27:30
【问题描述】:
过去几天我一直在学习 react 中的钩子,我尝试创建一个场景,我需要在屏幕上渲染一个大网格,并根据我想要采取的操作更新节点的背景颜色.有两个动作会改变节点的背景颜色,这两个动作必须同时存在。
- 单击节点时,光标悬停在节点上。
-
Grid 组件中存在一种算法,该算法将改变某些
节点。
在我看来,有多种方法可以实现这一点,但我在使用钩子的方式上遇到了一些麻烦。我将首先引导您完成我的思考过程,了解如何从我学到的知识中实现这一点,然后向您展示我尝试过的实现。我试图保留代码的重要部分,以便可以清楚地理解。如果我遗漏了什么或完全误解了一个概念,请告诉我。
-
孩子们可以保持自己的状态并知道如何更新自己。父级可以保存对列表中每个子级的引用,并在需要时从子级的引用中调用必要的函数以更新子级。
- 适用于要执行的第一个和第二个操作。此解决方案不会导致性能问题,因为子级管理自己的状态,并且如果父级通过引用更新子级状态,则唯一要重新渲染的子级将是被调用的那个。
- 从我阅读的内容来看,此解决方案被视为一种反模式。
- 适用于要执行的第一个和第二个操作。此解决方案不会导致性能问题,因为子级管理自己的状态,并且如果父级通过引用更新子级状态,则唯一要重新渲染的子级将是被调用的那个。
const Grid = () => {
// grid array contains references to the GridNode's
function handleMouseDown() {
setIsMouseDown(true);
}
function handleMouseUp() {
setIsMouseDown(false);
}
function startAlgorithm() {
// call grid[row][column].current.markAsVisited(); for some of the children in grid.
}
return (
<table>
<tbody>
{
grid.map((row, rowIndex) => {
return (
<tr key={`R${rowIndex}`}>
{
row.map((node, columnIndex) => {
return (
<GridNode
key={`R${rowIndex}C${columnIndex}`}
row={rowIndex}
column={columnIndex}
ref={grid[rowIndex][nodeIndex]}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
/>
);
})
}
</tr>
);
}
)
}
</tbody>
</table>
);
};
const GridNode = forwardRef((props, ref) => {
const [isVisited, setIsVisited] = useState(false);
useImperativeHandle(ref, () => ({
markAsVisited: () => {
setIsVisited(!isVisited);
}
}));
function handleMouseDown(){
setIsVisited(!isVisited);
}
function handleMouseEnter () {
if (props.isMouseDown.current) {
setIsVisited(!isVisited);
}
}
return (
<td id={`R${props.row}C${props.column}`}
onMouseDown={handleMouseDown}
onMouseEnter={handleMouseEnter}
className={classnames("node", {
"node-visited": isVisited
})}
/>
);
});
2. 子节点的状态可以作为来自父节点的props,任何更新操作都可以在父节点内部实现。 (子节点被正确更新,渲染仅在必要的子节点中被调用,但 DOM 似乎结结巴巴。如果你以一定的速度移动鼠标,什么都不会发生,并且每个访问的节点都会立即更新。)
- 不适用于第一个操作。子项被正确更新,渲染只在必要的子项中被调用,但 DOM 似乎结结巴巴。如果您以特定速度移动鼠标,则不会发生任何事情,并且每个访问的节点都会立即更新。
const Grid = () => {
// grid contains objects that have boolean "isVisited" as a property.
function handleMouseDown() {
isMouseDown.current = true;
}
function handleMouseUp() {
isMouseDown.current = false;
}
const handleMouseEnterForNodes = useCallback((row, column) => {
if (isMouseDown.current) {
setGrid((grid) => {
const copyGrid = [...grid];
copyGrid[row][column].isVisited = !copyGrid[row][column].isVisited;
return copyGrid;
});
}
}, []);
function startAlgorithm() {
// do something with the grid, update some of the "isVisited" properties.
setGrid(grid);
}
return (
<table>
<tbody>
{
grid.map((row, rowIndex) => {
return (
<tr key={`R${rowIndex}`}>
{
row.map((node, columnIndex) => {
const {isVisited} = node;
return (
<GridNode
key={`R${rowIndex}C${columnIndex}`}
row={rowIndex}
column={columnIndex}
isVisited={isVisited}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseEnter={handleMouseEnterForNodes}
/>
);
})
}
</tr>
);
}
)
}
</tbody>
</table>
);
};
const GridNode = ({row, column, isVisited, onMouseUp, onMouseDown, onMouseEnter}) => {
return useMemo(() => {
function handleMouseEnter() {
onMouseEnter(props.row, props.column);
}
return (
<td id={`R${row}C${column}`}
onMouseEnter={handleMouseEnter}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
className={classnames("node", {
"node-visited": isVisited
})}
/>
);
}, [props.isVisited]);
}
关于这个话题,我有两个问题想问。
在第一个实现中;当节点改变其状态时,父组件不会重新渲染。如果在这种情况下有益,只使用这种反模式是错误的吗?
第二个实现出现卡顿的原因可能是什么?我花了一段时间阅读文档并尝试了不同的东西,但找不到发生口吃的原因。
【问题讨论】:
标签: javascript reactjs react-hooks