Основы привязки данных и управления состоянием
Декларативная модель
React Spreadsheet следует декларативному подходу: вы храните данные листов в состоянии React, передаёте их как пропсы, а обёртка автоматически сравнивает ваши данные с текущим состоянием виджета и применяет только изменения.
import { useState } from "react";
import { ReactSpreadsheet, type SheetData } from "@dhtmlx/trial-react-spreadsheet";
import "@dhtmlx/trial-react-spreadsheet/spreadsheet.react.css";
function App() {
const [sheets, setSheets] = useState<SheetData[]>([
{
id: "sheet1",
name: "Data",
cells: {
A1: { value: "Hello" },
},
},
]);
return <ReactSpreadsheet sheets={sheets} activeSheet="sheet1" />;
}
Обновление ячеек
Используйте иммутабельные обновления состояния с функциональным обновлением setState:
const updateCell = (sheetId: string, cell: string, value: string | number) => {
setSheets((prev) =>
prev.map((sheet) =>
sheet.id === sheetId
? {
...sheet,
cells: {
...sheet.cells,
[cell]: { ...sheet.cells[cell], value },
},
}
: sheet
)
);
};
Реакция на действия пользователя
Используйте onAfterAction для обработки изменений, выполненных пользователем. Сочетайте его с ref, чтобы читать текущие данные виджета:
import { useRef } from "react";
import { ReactSpreadsheet, type SpreadsheetRef } from "@dhtmlx/trial-react-spreadsheet";
function App() {
const ref = useRef<SpreadsheetRef>(null);
const [sheets, setSheets] = useState<SheetData[]>([/* ... */]);
const handleAfterAction = () => {
const data = ref.current?.instance?.serialize();
if (data) {
// Синхронизация состояния виджета обратно в состояние React
console.log("Spreadsheet data:", data);
}
};
return (
<ReactSpreadsheet
ref={ref}
sheets={sheets}
onAfterAction={handleAfterAction}
/>
);
}
Escape-люк через ref
Для операций, которые не отображаются на декларативные пропсы, используйте SpreadsheetRef для доступа к экземпляру базового виджета:
- Сериализация данных:
ref.current?.instance?.serialize() - Отмена/возврат:
ref.current?.instance?.undo()/ref.current?.instance?.redo() - Получение значения ячейки:
ref.current?.instance?.getValue("A1") - Программный выбор:
ref.current?.instance?.selection.setSelectedCell("A1:C5")
const ref = useRef<SpreadsheetRef>(null);
const handleExport = () => {
const data = ref.current?.instance?.serialize();
console.log(data);
};
<ReactSpreadsheet ref={ref} sheets={sheets} />
Избегайте смешивания императивных записей (например, instance.setValue()) с декларативным пропом sheets. Обёртка может перезаписать императивные изменения при следующем цикле рендера. Используйте ref только для чтения данных и для операций вроде отмены/возврата, выбора и экспорта.
Управляемый поиск
Используйте проп search вместе с onSearchResults для управляемого поиска:
const [search, setSearch] = useState<SearchConfig | undefined>();
const [results, setResults] = useState<string[]>([]);
<input
placeholder="Search..."
onChange={(e) =/> setSearch({ query: e.target.value, open: true })}
/>
<p>{results.length} cells found</p>
<ReactSpreadsheet
sheets={sheets}
search={search}
onSearchResults={setResults}
/>
Отмена / возврат
Используйте onStateChange для отслеживания доступности отмены/возврата и вызывайте undo()/redo() через ref:
const ref = useRef<SpreadsheetRef>(null);
const [history, setHistory] = useState({ canUndo: false, canRedo: false });
<button
disabled={!history.canUndo}
onClick={() => ref.current?.instance?.undo()}
>
Undo
</button>
<button
disabled={!history.canRedo}
onClick={() => ref.current?.instance?.redo()}
>
Redo
</button>
<ReactSpreadsheet
ref={ref}
sheets={sheets}
onStateChange={setHistory}
/>
Производительность
- Используйте
useMemoдля производных листов, чтобы избежать лишних пересчётов:
const filteredSheets = useMemo(
() => sheets.filter((s) => s.name !== "Hidden"),
[sheets]
);
<ReactSpreadsheet sheets={filteredSheets} />
- Избегайте пересоздания объекта
stylesпри каждом рендере. Определяйте его вне компонента или оборачивайте вuseMemo. - Используйте функциональное обновление
setState, чтобы избежать проблем с устаревшим замыканием в колбэках событий.