Перейти к основному содержимому

Основы привязки данных и управления состоянием

Декларативная модель

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, чтобы избежать проблем с устаревшим замыканием в колбэках событий.