首页 > 解决方案 > React - 在本地状态下更新 2d 数组而不更新显示网格中的每个组件

问题描述

我正在使用react-window的VariableSizeGrid来显示网格。

该网格显示一些格式化的数字,当您单击一个单元格时,您可以更改“原始”数字(没有格式),并且在失去焦点时再次格式化(所以 onBlur)。该部分在 CodeSandbox 上似乎效果不佳,但在我的本地应用程序上效果很好

在我的实际应用程序中,reportData 是从 api 中检索的,并且delta是动态创建的(以及行数、列数……)我尽可能简化了代码以专注于我的问题。

我将reportDatadelta存储在本地组件状态中(使用 UseState)。在我的真实应用程序中,reportData 位于 redux 存储中,但这不相关。

我使用 (lodash) 以不可变的方式更新 delta,但我有一个主要问题。

每次我在输入单元格中输入一个数字时,我只能输入一个数字(然后我失去焦点)。因此,在我输入每个数字后,都会触发NumberField的 onChange 事件,这个事件调用Cell的 onChange ,它调用DynamicReport的 onChange ,然后更新 delta 的状态。该状态更新然后触发所有 Cell 的刷新(这就是为什么我不能输入/失去焦点)

我尝试添加一些去抖动,但这并没有真正解决我的问题。

在我的情况下,使用状态(所以本地状态)来存储我的报告数据和增量是正确的方法吗?

我在 CodeSandbox 中创建了我的示例:

https://codesandbox.io/s/input-grid-example-zpotp

也许我的方法存在根本缺陷,欢迎任何反馈/建议

请在下面找到我用于显示组件和报告/网格的代码

数字字段.tsx

import React, { ReactElement, useState } from "react";

interface NumberFieldProps {
  name: string;
  value: number;
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
}

const NumberField = (props: NumberFieldProps): ReactElement => {
  const [isEditing, setIsEditing] = useState(false);

  const onChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
    props.onChange(event);
  };

  const toFormattedNumber = (number: number) => {
    const formatter = new Intl.NumberFormat("de-CH", {
      style: "decimal",
      minimumFractionDigits: 2,
      maximumFractionDigits: 2
    });

    return formatter.format(number);
  };

  const toggleEditing = (): void => {
    console.log(
      "NumberFieldDemo toggleEditing called for name: %s",
      props.name
    );
    setIsEditing(!isEditing);
  };

  const generateField = (): ReactElement => {
    if (isEditing) {
      return (
        <input
          type="number"
          name={props.name}
          value={props.value}
          onChange={onChange}
          onBlur={toggleEditing}
          className="inputStd"
        />
      );
    } else {
      return (
        <div tabIndex={1} onFocus={toggleEditing}>
          {toFormattedNumber(props.value)}
        </div>
      );
    }
  };

  return <div>{generateField()}</div>;
};

export default NumberField;

报告.tsx

// React
import React, { ReactElement, useState } from "react";

// Lodash
import { cloneDeep } from "lodash";
import memoize from "memoize-one";

// react-window
import {
  VariableSizeGrid as Grid,
  GridChildComponentProps
} from "react-window";

// Formatting
import "./report.css";

// Own components
import NumberField from "./number-field";

const ReportDemoState = (): ReactElement => {
  const [width, setWidth] = useState(400);
  const [height, setHeight] = useState(100);

  const [reportData, setReportData] = useState([
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12]
  ]);
  const [delta, setDelta] = useState([
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0]
  ]);

  const Cell = ({
    columnIndex,
    rowIndex,
    style,
    data
  }: GridChildComponentProps): ReactElement => {
    const [value, setValue] = useState(
      reportData[rowIndex][columnIndex] + delta[rowIndex][columnIndex]
    );
    const onChange = (
      event: React.ChangeEvent<HTMLInputElement>,
      rowIndex: number,
      columnIndex: number
    ): void => {
      data.onChange(event, rowIndex, columnIndex);
    };

    const updateValue = (newValue: number) => {
      setValue(newValue);
    };

    return (
      <div className="GridItem" style={style}>
        <NumberField
          name={String(rowIndex) + ":" + String(columnIndex)}
          value={value}
          onChange={(event) => {
            updateValue(Number(event.target.value));
            onChange(event, rowIndex, columnIndex);
          }}
        ></NumberField>
      </div>
    );
  };

  const createItemData = memoize((onChange) => ({
    onChange
  }));

  const DynamicReport = () => {
    const columnWidths = new Array(4).fill(true).map(() => 50);
    const rowHeights = new Array(3).fill(true).map(() => 25);

    const onChange = (
      event: React.ChangeEvent<HTMLInputElement>,
      rowIndex: number,
      columnIndex: number
    ) => {
      let newDelta = cloneDeep(delta);
      newDelta[rowIndex][columnIndex] =
        Number(event.target.value) - Number(reportData[rowIndex][columnIndex]);
      setDelta(newDelta);
    };

    const itemData = createItemData(onChange);

    return (
      <Grid
        className="Grid"
        columnCount={4}
        columnWidth={(index) => columnWidths[index]}
        height={height}
        rowCount={3}
        rowHeight={(index) => rowHeights[index]}
        width={width}
        itemData={itemData}
      >
        {Cell}
      </Grid>
    );
  };

  return (
    <div className="full-page">
      <p>Report Demo State</p>
      <DynamicReport />
    </div>
  );
};

export default ReportDemoState;

报告.css

.Grid {
  border: 1px solid #d9dddd;
}

.GridItem {
  display: flex;
  align-items: center;
  justify-content: center;
}

.inputStd {
  width: 50px;
}

标签: reactjsmultidimensional-arraystate

解决方案


推荐阅读