Pir*_*han 5 javascript reactjs react-hooks
过去几天我一直在学习 hooks in react,我尝试创建一个场景,我需要在屏幕上渲染一个大网格,并根据我想要采取的操作更新节点的背景颜色。有两个动作会改变节点的背景颜色,这两个动作必须共存。
在我看来,有多种方法可以实现这一点,但是我在使用钩子的方式方面遇到了一些麻烦。我将首先向您介绍如何从我所学到的知识中实现这一点的思考过程,然后向您展示我尝试过的实现。我试图保留代码的重要部分,以便可以清楚地理解。如果我遗漏了什么或完全误解了一个概念,请告诉我。
孩子们可以保持自己的状态,知道如何更新自己。父级可以保存对列表中每个子级的引用,并在需要时从子级的引用中调用必要的函数以更新子级。
    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 似乎卡住了。如果你以一定的速度移动鼠标,什么都不会发生,并且每个被访问的节点都会立即更新。)
    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]);
    }
关于这个话题,我有两个问题要问。
在第一个实现中;当节点更改其状态时,父组件不会重新渲染。如果这种反模式在这种情况下有益,那么仅仅利用这种反模式是错误的吗?
第二个实现遇到的口吃可能是什么原因?我花了一段时间阅读文档并尝试不同的东西,但找不到发生口吃的原因。
正如您所说,使用 refs 控制子数据是一种反模式,但这并不意味着您不能使用它。
这意味着,如果有更好、性能更高的方法,最好使用它们,因为它们可以提高代码的可读性并改善调试。
在您的情况下,使用 ref 肯定可以更轻松地更新状态,并且还可以防止大量重新渲染,这是实现上述解决方案的好方法
造成第二个实现出现卡顿的原因可能是什么?我花了一段时间阅读文档并尝试不同的东西,但找不到发生口吃的原因。
第二个解决方案中的许多问题源于这样一个事实:您定义了在每次重新渲染时重新创建的函数,因此导致整个网格而不只是单元格被重新渲染。利用 useCallback 来记忆 Grid 组件中的这些函数
另外,您应该在 GridNode 中使用React.memo而不是useMemo用于您的用例。
另一件需要注意的事情是,您在更新时会改变状态,相反,您应该以不可变的方式更新它
工作代码:
const Grid = () => {
  const [grid, setGrid] = useState(getInitialGrid(10, 10));
  const isMouseDown = useRef(false);
  const handleMouseDown = useCallback(() => {
    isMouseDown.current = true;
  }, []);
  const handleMouseUp = useCallback(() => {
    isMouseDown.current = false;
  }, []);
  const handleMouseEnterForNodes = useCallback((row, column) => {
    if (isMouseDown.current) {
      setGrid(grid => {
        return grid.map((r, i) =>
          r.map((c, ci) => {
            if (i === row && ci === column)
              return {
                isVisited: !c.isVisited
              };
            return c;
          })
        );
      });
    }
  }, []);
  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;
                if (isVisited === true) console.log(rowIndex, columnIndex);
                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
}) => {
  function handleMouseEnter() {
    onMouseEnter(row, column);
  }
  const nodeVisited = isVisited ? "node-visited" : "";
  return (
    <td
      id={`R${row}C${column}`}
      onMouseEnter={handleMouseEnter}
      onMouseDown={onMouseDown}
      onMouseUp={onMouseUp}
      className={`node ${nodeVisited}`}
    />
  );
};
PS 虽然useCallback和其他记忆将有助于提供一些性能优势,但它仍然无法克服对状态更新和重新渲染的性能影响。在这种情况下,最好在子级中定义状态并公开父级的引用
| 归档时间: | 
 | 
| 查看次数: | 288 次 | 
| 最近记录: |