React Gantt 数据绑定与状态管理
React Gantt 支持 两种数据绑定模式:
- 以 React state 为真相来源(推荐) - 大多数 React 应用的首选。
- 以 Gantt 为真相来源 - 在某些专门场景下很有用。
两种方式都有效,但应选择一种并持续遵循,以避免出现意外行为。
本文将解释这两种模式,并展示各自的基础示例。
如果你还没有渲染一个基础图表,请从 Quick Start 开始。
数据模型
以 React state 为真相来源(推荐)
在这种模型中:
- 你将
tasks、links、resources、resourceAssignments保存在 React state 或者某个状态库中 - 将它们作为 props 传递给
<Gantt> - 当用户修改了什么时,ReactGantt 会调用你的
data.save或data.batchSave回调 - 你更新 React state -> React 重新渲染 -> ReactGantt 重新读取最新的 props
这是在你的页面上还存在其他需要与 Gantt 看到同一数据的 React UI,且应用中还有使用相同数据的其他 React 组件,或使用依赖同一数据的状态管理器时的正确选择。
不过,它会带来对 Gantt 的重新解析或重新渲染的频率增加的问题。
以 Gantt 为真相来源
在这种做法中,你将 ReactGantt 和后端视为数据的主要所有者:
- ReactGantt 通过
data.load、或通过 props、或通过一个命令式 API 调用加载初始数据集 - ReactGantt 在内部应用用户更改,或将其发送到服务器
- 你不保留一份在 React state 中的完整任务/链接镜像,并不断传回 props
关键区别在于不存在完整的循环——用户更改不会更新 React state,且每次更改后 React 也不会重新应用更新后的 props。
当数据集非常大时,这种模型很有用,因为它减少了在 Gantt 数据变化时持续更新 React state 的开销,并简化大批量操作(如自动排程)而无需重复重新渲染。
另一方面,你会失去 Gantt 数据与 React state 之间的直接同步。如果你确实将任务/链接存储在 React state 中,则需要确保不会无意中覆盖 Gantt 的内部状态。
以 React state 为真相来源
在此模式中,你将所有核心集合保存在 state 中并将它们作为 props 传递(tasks、links、resources、resourceAssignments)。每当用户在 Gantt 内修改任务或链接(例如,通过创建或删除任务),Gantt 会触发回调。在这个回调中,你用新数据来更新你的 React state。一旦 state 更新,React 会重新渲染 ReactGantt 组件,进而从最新的 state 读取更新后的 props。
在定义 state 的类型时,针对任务使用 SerializedTask,针对链接使用 SerializedLink。这些类型表示面向用户的数据结构——日期字段接受 Date | string,并且没有内部以 $ 前缀的属性。仅在 Gantt 事件处理程序内工作数据时,才使用 Task 和 Link,其中 Gantt 已经解析了数据。
使用 React state 的最小示例
import { useState } from 'react';
import Gantt, {
Task,
Link
} from "@dhtmlx/trial-react-gantt";
import "@dhtmlx/trial-react-gantt/dist/react-gantt.css";
import { demoData } from "./demoData";
export default function ReactStateGantt() {
const [tasks, setTasks] = useState<Task[]>(demoData.tasks);
const [links, setLinks] = useState<Link[]>(demoData.links);
return (
<div style={{ height: "100vh" }}>
<Gantt
tasks={tasks}
links={links}
data={{
save: (entity, action, item, id) => {
// 在这里更新 React state(下面的模式请参阅)
console.log("Change:", { entity, action, id, item });
},
}}
/>
</div>
);
}
这为你提供了一个基础起点——React 通过 props 控制渲染,Gantt 通过 save 回调报告变更,并使 React 成为数据的权威所有者。
下文将展示在该回调中你通常实现的典型模式。
使用 data.save 处理变更
当你提供 data.save 时,ReactGantt 会在用户进行的每一次变更上调用它:
(entity: string, action: string, item: any, id: string|number) => {...}
其中:
entity是"task" | "link" | "resource" | "resourceAssignment"action是"create" | "update" | "delete"item是创建/更新/删除 的对象id是对象的 id
下面是一个简单的示例,直接更新 React state:
function handleSave(entity, action, item, id) {
if (entity === "task") {
setTasks((prev) => {
if (action === "create") return [...prev, item];
if (action === "update") return prev.map((t) => (t.id === id ? item : t));
if (action === "delete") return prev.filter((t) => t.id !== id);
return prev;
});
}
if (entity === "link") {
setLinks((prev) => {
if (action === "create") return [...prev, item];
if (action === "update") return prev.map((l) => (l.id === id ? item : l));
if (action === "delete") return prev.filter((l) => l.id !== id);
return prev;
});
}
// 如有需要,你也可以对 resources / assignments 应用同样的思路
}
在实际应用中,几乎从不把这段逻辑直接内联:
- 在 Redux Toolkit 中,这会成为一个 reducer 或 thunk
- 在 Zustand/Jotai/MobX/Valtio 中,它会驻留在 store 中
- 对于服务器集成,你也可以在这里调用 API