https://github.com/jonmaxmue/context/tree/main
Was ist „Context“? #
- Zweck: Der Context liefert eine wiederverwendbare Applikations‑Infrastruktur: State‑Management, Controller‑Wiring, Repositories, IO über Directus, Reaktivität (MobX), Routing‑Hooks und einen Provider für deine UI.
- Prinzip: Du definierst deine Domäne (eigene Models, optionale Controller, eigene UI). Der Context übernimmt den Rest (Core‑Logik).
- Nutzen:
- Schneller Start: Plug & Play – nur Provider einbinden und
moduleConfigdefinieren. - Konsistenz: Einheitliches Schema für Models und Controller, abgestimmt auf Directus.
- Reaktivität: MobX +
observeraktualisieren deine UI automatisch. - Trennung: Fachlogik (Models/Controller) bleibt sauber getrennt von Infrastruktur (Repos/IO/Navigation).
- Schneller Start: Plug & Play – nur Provider einbinden und
Wichtige Bausteine im Projekt #
- Provider: providers/ContextControllerProvider.tsx
Bindet den Context global ein und macht ihn per Hook verfügbar:- Hook: useContextControllerProvider() liefert dir den
controller. - Beispiel im Code: ContextControllerProvider erstellt
new Controller(moduleConfig, routeConfig, routerFunctions)und rendert erst, wenncontroller.loadedtrue ist.
- Hook: useContextControllerProvider() liefert dir den
- Modul‑Konfiguration: configs/moduleConfig.ts
Deklariert Models (Collection, Klassentyp) und initialisiert Controller. Alle Module sind darüber einheitlich verdrahtet. - Routing:
configs/routeConfig.ts+ Expo Router
ContextControllerProvider injiziertrouter.push/replacein den Context, damit Controller navigieren können. - Reaktivität: MobX (
makeAutoObservable/makeObservable)
Models sind observabel, Controller kapseln Lade‑/Mutationslogik. In der UI verwendest dumobx-react’sobserver.
Eigene Controller und Models mit Context integrieren #
Mit Context kannst du eigene Controller im selben Schema integrieren und eigene Models im vorgegebenen MobX‑Muster definieren. Der große Vorteil: Dein Projekt kann seine Domänenobjekte frei gestalten und nutzt Context, um diese zentral zu verwalten – inklusive Directus‑IO und Reaktivität – bis hin zur eigenen UI.
Muster: Controller mit initialize(...) #
Controller werden in der moduleConfig registriert und „lazy“ mit Abhängigkeiten verdrahtet. Das passt zu deinem bestehenden Pattern (siehe controllers/examination/ExaminationController.ts: initialize(examinationItemController, gameRelationController, gameId)).
Beispielcontroller (Schema):
// controllers/MyFeatureController.ts
import { makeObservable, observable, action, runInAction } from "mobx";
import { ItemController } from "context";
export default class MyFeatureController {
_itemController!: ItemController<any>;
_relationController?: any;
_entityId?: number;
isLoading = false;
constructor() {
makeObservable(this, {
_itemController: observable,
_relationController: observable,
_entityId: observable,
isLoading: observable,
loadById: action,
clear: action,
});
}
// Wird von moduleConfig/Context aufgerufen
initialize(
itemController: ItemController<any>,
relationController: any,
entityId: number
) {
this._itemController = itemController;
this._relationController = relationController;
this._entityId = entityId;
}
get store() {
return this._itemController.store;
}
get selected() {
return this._itemController.store.getSelected();
}
async loadById(id: number) {
this.isLoading = true;
try {
await this._itemController.loadOne(id);
} finally {
runInAction(() => (this.isLoading = false));
}
}
clear() {
this._itemController.store.clearSelected();
}
}
- Analog zu deinem
ExaminationController: Konstruktor setzt nur Basisstate;initialize(...)verdrahtet late‑bound Abhängigkeiten (ItemController, RelationController, IDs).
Muster: Model mit asJson() und updateFromJson(…) #
Models folgen einem festen Schema (vgl. models/items/CohortPatientModel.ts,
models/items/GameClinicalDirective.ts):
- asJson(): TItem – Serialisierung für Write‑IO (Directus).
- updateFromJson(json: TItem): this – Mapping von API → Model.
- Optional: getNewInstance() wenn dein Store es nutzt.
- Reaktivität via MobX.
Beispielmodell (Struktur‑Vorlage):
// models/items/MyFeatureModel.ts
import { makeAutoObservable } from "mobx";
import { TItem } from "context";
export default class MyFeatureModel {
id: number;
title: string;
relatedId?: number;
constructor() {
this.id = 0;
this.title = "";
this.relatedId = undefined;
makeAutoObservable(this);
}
get asJson(): TItem {
return {
id: this.id,
title: this.title,
related_id: this.relatedId, // API-konforme Feldnamen
};
}
updateFromJson(json: TItem) {
this.id = json.id;
this.title = json.title;
this.relatedId = json.related_id;
return this;
}
getNewInstance() {
return new MyFeatureModel();
}
}
- Richte dich beim Mapping nach deinen Collections (z.B.
patients_id,games_idin GameClinicalDirective,patients_idinCohortPatientModel).
Registrierung in moduleConfig #
Im Projekt werden Controller/Models in
configs/moduleConfig.ts gemäß Schema zusammengeführt (siehe vorhandene Einträge wie GameController, GameActivityController, etc.).
Beispiel (Ausschnitt):
// configs/moduleConfig.ts
import MyFeatureController from "@/controllers/MyFeatureController";
import MyFeatureModel from "@/models/items/MyFeatureModel";
import { ModuleConfig, ItemController } from "context";
export const myModuleConfig: ModuleConfig = {
models: {
myFeature: {
model: MyFeatureModel,
collection: "my_feature",
},
},
controllers: (deps) => {
const myFeatureItemController = new ItemController({
model: MyFeatureModel,
collection: "my_feature",
client: deps.client, // Directus-Client aus Context
notificationController: deps.notificationController,
});
const myFeature = new MyFeatureController();
myFeature.initialize(
myFeatureItemController,
deps.someRelationController, // falls benötigt
0
);
return {
myFeatureController: myFeature,
};
},
};
- Halte dich an das vorhandene Initialisierungsmuster deiner Controller (z.B.
ExaminationController.initialize(...)).
Verwendung in der UI #
- Provider ist bereits integriert (providers/ContextControllerProvider.tsx):
- Baut
new Controller(moduleConfig, routeConfig, routerFunctions). - Rendert Kinder erst, wenn
controller.loadedtrue ist.
- Baut
- Zugriff in Screens/Komponenten:
import { observer } from "mobx-react";
import { useContextControllerProvider } from "context";
import { useEffect } from "react";
export default observer(function MyFeatureScreen() {
const ctx = useContextControllerProvider();
const myFeature = ctx.modules.myModule.controllers.myFeatureController;
useEffect(() => {
myFeature.loadById(1);
}, []);
if (myFeature.isLoading) return null;
return <Text>{myFeature.selected?.title}</Text>;
});
- Reaktivität:
observersorgt dafür, dass UI bei Änderungen im Store/Controller automatisch aktualisiert.
Kurzfazit #
Definiere eigene Models und (falls nötig) Controller. Registriere sie in der moduleConfig. Binde den Provider ein. Ab da übernimmt Context als Core deine zentrale App-Logik – inkl. Auth-Flow. Du fokussierst dich auf Domäne und UI. Plug & Play.