GrundbegriffeGilt fürLite und Pro
React-Integration, Refs und Lebenszyklus
Rendern Sie SuperSchedulerComponent mit den Optionen des Planers als Props. Nach dem Mounten erreichen Sie das Control über ref.current.control, eine Prop controlRef oder useSchedulerControl(), das es Ihnen zusätzlich als State liefert. Die Größe legen Sie mit height und heightSpec fest. Halten Sie Objekt- und Funktions-Props stabil, denn nur Props mit geänderter Identität erreichen control.update(), und verlassen Sie sich darauf, dass die Komponente pro Mount ein neues Control erzeugt und es beim Unmounten freigibt; das macht Strict Mode sicher.
SuperSchedulerComponent ist ein schlanker React-Host um ein DOM-Control, SuperScheduler.Scheduler. React rendert ein einziges leeres <div>; das Control baut und aktualisiert alles darin, und Scrollen, Zoomen und Ziehen laufen ohne React-Renderings. Ihr React-Code beschreibt die Konfiguration als Props und spricht für imperative Aktionen, etwa das Scrollen zu einem Datum, mit dem Control.
Diese Seite behandelt die Pro-Komponente. Lite folgt denselben Konventionen mit weniger Optionen; die Unterschiede stehen am Ende.
Die Komponente mounten
Jede Option des Planers ist eine Prop, und jeder onXxx-Handler ebenfalls:
import { useMemo } from 'react'
import { SuperSchedulerComponent } from 'super-scheduler'
import type { SuperScheduler } from 'super-scheduler'
import 'super-scheduler/styles.css'
// Module constants: the same identity on every render, so they are applied once.
const ROOMS: SuperScheduler.ResourceData[] = [
{ id: 'r101', name: 'Room 101' },
{ id: 'r102', name: 'Room 102' },
{ id: 'r103', name: 'Room 103' },
]
const TIME_HEADERS: SuperScheduler.TimeHeaderData[] = [
{ groupBy: 'Month' },
{ groupBy: 'Day', format: 'd' },
]
export function Planning({ bookings }: { bookings: SuperScheduler.EventData[] }) {
// The control adopts the array it receives and edits it in place: give it its own copy.
const owned = useMemo(() => bookings.slice(), [bookings])
return (
// The component renders a bare <div> with no className or style props: lay it out through
// a wrapper, and style the control's root with cssClass (or classNames.root).
<section className="planning" aria-label="Room planning">
<SuperSchedulerComponent
startDate="2026-10-01"
days={31}
scale="Day"
cellWidth={44}
timeHeaders={TIME_HEADERS}
resources={ROOMS}
events={owned}
height={480}
heightSpec="Fixed"
cssClass="planning__scheduler"
/>
</section>
)
}Sie sollten einen 480 Pixel hohen Abschnitt mit einem Monat an Tagesspalten und drei Zimmern sehen. Die Komponente selbst akzeptiert weder className noch style noch id: Positionieren Sie sie über ein umschließendes Element, und gestalten Sie das Wurzelelement des Controls mit cssClass oder den Props classNames und styles, wie in Themes beschrieben.
Die reinen React-Props (controlRef, children, key, ref) bleiben in React. Jede andere Prop wird an das Control übergeben, auch Namen, die die Typdefinitionen nicht deklarieren; eine falsch geschriebene Option meldet die Komponente also nicht. Verlassen Sie sich darauf, dass TypeScript sie findet.
Das Control erreichen
Das Control existiert erst, nachdem die Komponente gemountet ist. Es gibt drei Wege dorthin:
| Methode | Was Sie erhalten | Verwenden Sie sie für |
|---|---|---|
ref auf der Komponente | ref.current.control | Effects und Event-Handler in derselben Komponente |
Prop controlRef | Ein Ref-Objekt, dessen current das Control ist, oder einen Callback, der beim Mounten damit aufgerufen wird | Die Übergabe des Controls an eine Elternkomponente oder an Code außerhalb von React |
useSchedulerControl() | { controlRef, control }: das Ref und dazu das Control als React-State | Effects, die laufen müssen, sobald das Control erscheint, etwa zum Erzeugen von Widgets |
import { useEffect, useRef } from 'react'
import { SuperScheduler, SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SchedulerEventMovedArgs } from 'super-scheduler'
const ROOMS: SuperScheduler.ResourceData[] = [{ id: 'r101', name: 'Room 101' }]
// 3. Inside handlers the control is `args.control` (and `this` in a non-arrow function).
function announceMove(args: SchedulerEventMovedArgs) {
args.control.message(`Moved to ${args.newStart.toString('d MMM')}`)
}
// 1. A ref to the component: `ref.current.control` exists after mount.
export function WithComponentRef() {
const ref = useRef<SuperSchedulerComponent>(null)
useEffect(() => {
ref.current?.control.scrollTo('2026-10-15', false, 'middle')
}, [])
return (
<SuperSchedulerComponent
ref={ref}
startDate="2026-10-01"
days={31}
scale="Day"
resources={ROOMS}
/>
)
}
// 2. useSchedulerControl(): a stable ref for handlers, plus the control as state for effects.
export function WithHook() {
const { controlRef, control } = useSchedulerControl()
useEffect(() => {
// `control` is null on the first render; the effect runs again once the scheduler mounts.
control?.scrollTo(SuperScheduler.Date.today(), 'fast', 'middle')
}, [control])
const notify = () => controlRef.current?.message('Saved', 2000)
return (
<>
<button type="button" onClick={notify}>
Notify
</button>
<SuperSchedulerComponent
controlRef={controlRef}
startDate="2026-10-01"
days={31}
scale="Day"
resources={ROOMS}
onEventMoved={announceMove}
/>
</>
)
}In der Praxis kommt es auf einige Details an:
- Lesen Sie das Control nie während des Renderns. Beim ersten Rendering existiert es noch nicht. Lesen Sie es in Effects, Event-Handlern und Callbacks des Planers.
useSchedulerControl()kostet ein zusätzliches Rendering.controlist beim ersten Renderingnullund wird nach dem Mounten zum Control, sodass Effects, die von[control]abhängen, im richtigen Moment laufen. Das zurückgegebenecontrolRefist stabil und lässt sich in Handlern lesen, ohne auf dieses Rendering zu warten.- Ein
controlRef-Objekt wird beim Unmounten geleert (aufnullgesetzt, solange es noch auf dieses Control zeigt). EincontrolRef-Callback wird beim Mounten mit dem Control aufgerufen und beim Unmounten nicht mitnull. - Handler erhalten das Control. Viele Handler-Argumente enthalten
args.control, und in jedem Handler, der als normalefunctiongeschrieben ist, istthisdas Control.
Damit React neu rendert, wenn sich der Zustand des Planers ändert (Auswahl, Zoom, Viewport, Verlauf), bietet super-scheduler/hooks den Hook useScheduler({ track: [...] }). Er gibt { controlRef, control, state } zurück und aktualisiert nur für die Themen, die Sie verfolgen, nie einmal pro Animationsframe.
Die Größe des Planers festlegen
Das Control füllt die Breite seines Elternelements. Seine Höhe steuern zwei Optionen:
heightSpec | Verhalten von height |
|---|---|
'Max' (Standard) | Der Planer ist so hoch wie sein Inhalt, höchstens height Pixel (Standard 600); darüber hinaus scrollt er vertikal |
'Fixed' | Genau height Pixel, unabhängig von der Anzahl der Zeilen |
'Auto' | So hoch wie sein Inhalt, ohne eigene vertikale Scrollleiste |
'Parent100Pct' | Füllt die Höhe des Elternelements |
height ist die Gesamthöhe, Zeitköpfe und horizontale Scrollleiste eingeschlossen; Sie müssen also nichts für die Köpfe herausrechnen. height="100%" ist eine Kurzform, um das Elternelement zu füllen. Das Elternelement braucht dann eine bestimmte Höhe:
import { SuperSchedulerComponent } from 'super-scheduler'
import type { SuperScheduler } from 'super-scheduler'
interface FullHeightProps {
readonly rooms: SuperScheduler.ResourceData[]
readonly bookings: SuperScheduler.EventData[]
}
export function FullHeightPlanning({ rooms, bookings }: FullHeightProps) {
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
<header>Planning</header>
{/* A definite height for the scheduler to fill; minHeight 0 lets the flex item shrink. */}
<main style={{ flex: 1, minHeight: 0 }}>
<SuperSchedulerComponent
height="100%"
startDate="2026-10-01"
days={31}
scale="Day"
resources={rooms}
events={bookings}
/>
</main>
</div>
)
}control.setHeight(px) ändert die Höhe imperativ und schaltet auf 'Fixed' um. Innerhalb von SchedulerPanes verwaltet stattdessen die Bereichskomponente die Höhe; siehe Bereiche und gespeicherte Ansichten.
Identität von Props und Memoisierung
Bei jedem React-Update vergleicht die Komponente jede Prop mit Object.is mit ihrem vorherigen Wert und gibt nur die geänderten an control.update() weiter. Unveränderte Props kosten nichts. Geänderte Props lösen ein synchrones Neuzeichnen dessen aus, was sie betreffen. Drei Folgen:
- Inline-Objekte und -Arrays gelten bei jedem Rendering als „geändert“. Inline geschriebene
timeHeaders={[{ groupBy: 'Day' }]}oderresources={rows.map(...)}werden bei jedem Rendering der Elternkomponente erneut gesendet. - Auch Inline-Funktionen ändern sich bei jedem Rendering. Ein neues
onBeforeEventRendermacht das Rendering aller Ereignisse ungültig; ein neuesonBeforeCellRenderverwirft den Cache pro Zelle. - Eine Prop, die Sie entfernen, fällt auf den Standardwert der Bibliothek zurück. Wird eine Prop per Spread bedingt mal übergeben und mal nicht, wechselt sie zwischen Ihrem Wert und dem Standard hin und her.
Halten Sie Props mit Modulkonstanten, useState, useMemo und useCallback stabil. Bewährt hat sich ein memoisiertes Konfigurationsobjekt für Optionen und Handler, während die Daten separat übergeben werden:
import { useMemo, useState } from 'react'
import { SuperSchedulerComponent } from 'super-scheduler'
import type { SchedulerProps, SuperScheduler } from 'super-scheduler'
const TIME_HEADERS: SuperScheduler.TimeHeaderData[] = [
{ groupBy: 'Month' },
{ groupBy: 'Day', format: 'd' },
]
interface BoardProps {
readonly rooms: SuperScheduler.ResourceData[]
readonly bookings: SuperScheduler.EventData[]
/** Should be stable (useCallback in the parent): it is a dependency of the config below. */
readonly onOpen: (id: string) => void
}
export function Board({ rooms, bookings, onOpen }: BoardProps) {
const [compact, setCompact] = useState(false)
// Options and handlers in one memoized object: a parent re-render that changes none of the
// dependencies sends nothing to the control.
const config = useMemo<SchedulerProps>(
() => ({
startDate: '2026-10-01',
days: 31,
scale: 'Day',
cellWidth: compact ? 28 : 44,
density: compact ? 'compact' : 'comfortable',
timeHeaders: TIME_HEADERS,
onBeforeEventRender: (args) => {
args.data.cssClass = compact ? 'booking booking--compact' : 'booking'
},
onEventClick: (args) => onOpen(String(args.e.id())),
}),
[compact, onOpen],
)
const owned = useMemo(() => bookings.slice(), [bookings])
return (
<>
<button type="button" aria-pressed={compact} onClick={() => setCompact((value) => !value)}>
Compact
</button>
<SuperSchedulerComponent {...config} resources={rooms} events={owned} />
</>
)
}Sie sollten sehen, wie die Tafel beim Drücken des Buttons zwischen komfortabler und kompakter Dichte wechselt, während Renderings der Elternkomponente ohne Bezug dazu nichts an das Control senden.
Strict Mode, Unmounten und Freigeben
Die Komponente erzeugt in componentDidMount ein neues SuperScheduler.Scheduler und ruft in componentWillUnmount dessen dispose() auf. In der Entwicklung mountet React Strict Mode, unmountet und mountet erneut: Sie erhalten ein erstes Control, das sofort freigegeben wird, und ein zweites, das bleibt. Nichts leckt, aber Ihr eigener Code muss derselben Disziplin folgen:
- Geben Sie aus jedem Effect, der etwas an das Control hängt, eine Cleanup-Funktion zurück (Zoom-Widgets, eine Minimap, Listener, Timer). Ein Widget, das für das erste, bereits freigegebene Control erzeugt wurde, ist nutzlos und muss ebenfalls freigegeben werden.
- Sichern Sie asynchrone Callbacks ab. Eine Anfrage, die erst antwortet, nachdem der Nutzer die Seite verlassen hat, trifft womöglich auf ein freigegebenes Control. Prüfen Sie
control.disposed(), bevor Sie es aufrufen: Aufrufe auf einem freigegebenen Control können einen Fehler werfen. - Nach dem Unmounten ist
ref.current.controldas freigegebene Control, undcontrol.disposed()gibttruezurück. Refs, die übercontrolRefunduseSchedulerControl()entstanden sind, werden aufnullzurückgesetzt.
Gibt Ihre Anwendung das Control selbst frei, bemerkt die Komponente das und sendet ihm keine Updates mehr.
Server-Rendering
Alle Einstiegspunkte lassen sich in Node ohne DOM importieren; Server-Rendering und Prerendering stürzen also nicht ab. Die Ausgabe des Servers ist nur das leere Host-<div>: Das Control wird im Browser erzeugt, wenn die Komponente gemountet wird. Reservieren Sie den Platz mit einem umschließenden Element fester Größe und zeigen Sie, falls der erste Paint zählt, bis zum Mounten einen Platzhalter. Siehe SSR und Prerendering.
Ohne React: der imperative Host
Dasselbe Control funktioniert auf jedem Element, das Ihnen gehört, etwa in der Komponente eines anderen Frameworks oder auf einer Legacy-Seite:
import { SuperScheduler } from 'super-scheduler'
import 'super-scheduler/styles.css'
/** Mounts a scheduler into an element you own and returns its teardown. */
export function mountPlanning(host: HTMLElement): () => void {
const control = new SuperScheduler.Scheduler(host, {
startDate: '2026-10-01',
days: 31,
scale: 'Day',
resources: [
{ id: 'r101', name: 'Room 101' },
{ id: 'r102', name: 'Room 102' },
],
events: [
{
id: 1,
resource: 'r101',
start: '2026-10-02T14:00:00',
end: '2026-10-05T11:00:00',
text: 'Booking 1042',
},
],
onEventMoved: (args) => console.info('moved', args.e.id(), args.newStart.value),
})
// Required: nothing is rendered before init(), and update() before init() throws.
control.init()
// Later changes go through update(), which repaints synchronously.
control.update({ cellWidth: 56 })
// dispose() releases the control's DOM and listeners when the host goes away.
return () => control.dispose()
}new SuperScheduler.Scheduler(elementOrId, options)akzeptiert ein Element oder dessen ID.init()ist Pflicht;update()vorinit()wirft eineSuperScheduler.Exception.update(options)wendet Optionen an und zeichnet synchron neu.update()ohne Argument ist eine vollständige Aktualisierung, die die Scrollposition behält, aber die Auswahl von Zeiträumen und den Tastaturfokus aufhebt.dispose()liegt in diesem Modus in Ihrer Verantwortung.
Der Einstiegspunkt des Pakets exportiert auch die React-Komponente; React bleibt also eine installierte Peer-Abhängigkeit, selbst wenn Sie nur den imperativen Host verwenden.
Lite
super-scheduler-lite exportiert eine Komponente mit demselben Namen und denselben Ref-Konventionen: ref.current.control und eine Prop controlRef. Die Unterschiede: Es gibt kein useSchedulerControl; ein controlRef-Callback wird beim Unmounten mit null aufgerufen; height ist immer eine feste Höhe; und das Control hat nur update, scrollTo, scrollToResource, visibleStart, visibleEnd, disposed, dispose und init. Siehe Schnellstart mit Lite.
Nächste Schritte
- Ereignisse im React-State halten: Kontrollierte Ereignisse und Callbacks.
- React-Komponenten in Ereignisse und Köpfe setzen: React-Render-Slots.
- Große Datenmengen messen und optimieren: Performance und Virtualisierung.