reactjs - React - 在本地状态下更新 2d 数组而不更新显示网格中的每个组件
问题描述
我正在使用react-window的VariableSizeGrid来显示网格。
该网格显示一些格式化的数字,当您单击一个单元格时,您可以更改“原始”数字(没有格式),并且在失去焦点时再次格式化(所以 onBlur)。该部分在 CodeSandbox 上似乎效果不佳,但在我的本地应用程序上效果很好
在我的实际应用程序中,reportData 是从 api 中检索的,并且delta是动态创建的(以及行数、列数……)我尽可能简化了代码以专注于我的问题。
我将reportData和delta存储在本地组件状态中(使用 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;
}
解决方案
推荐阅读
- javascript - 当 Canvas 被 Web Worker 中的 OffscreenCanvas 隐藏并绘制到其上时,HTMLCanvasElement 捕获的流在 Chrome 上滞后是否正常?
- visual-c++ - 将 CString::GetString() 与 CWnd::SendMessage() 一起使用是否安全?
- mongodb - 将图像与 mongodb 项目链接的最佳方法是什么?
- javascript - D3中的垂直冰柱
- prolog - 是否有 atom/1 或 stream/1 等的 Prolog 名称(SWI-Prolog)
- actions-on-google - 令牌有效负载为空
- javascript - present : past[past.length - 1] ,这应该返回一个值,但它返回一个完整的数组,就像 slice 方法一样
- node.js - 将文件从 /tmp 下载到 Google Cloud Function 中的客户端
- python - 为 LightGBM 提供额外的自定义指标以提前停止
- sql - SQL:创建过程和调用过程