Skip to content

Frontend-Frameworks

Frontend-Beispiele öffnen Scopes für Seiten, Routen, Screens oder Features. Sie verwenden eigene kleine Builder statt des gemeinsamen Servermoduls.

Vergleiche, wo der Scope erstellt wird, wie Kindkomponenten darauf zugreifen und wie die Freigabe beim Entfernen erfolgt.

BeispielInhalt
react.tsxVon @inferdi/react verwalteter Feature-Scope
react-native.tsxScope für einen React-Native-Screen
vue.tsScope-Grenze über provide/inject in Vue 3
svelte.tsScope-Grenze über den Svelte-Kontext

React

tsx
import { Container } from '@inferdi/inferdi'
import {inferdiReact} from '@inferdi/react'
import {type PropsWithChildren} from 'react'

/*
 * Frontend examples keep their own minimal builder because the browser does
 * not have the `Database` / `process.env` shape that `_shared/container.ts`
 * uses on the server. The patterns are the same: scoped `FeatureContext`,
 * singleton services on the root, page-level scope after commit
 */

type FeatureContext = {
  readonly featureName: string
}

class ApiClient {
  async listProjects() {
    return [{ id: 'project_1', name: 'Docs' }]
  }
}

class ProjectsViewModel {
  constructor(
    private readonly feature: FeatureContext,
    private readonly api: ApiClient
  ) {}

  async load() {
    return {
      feature: this.feature.featureName,
      projects: await this.api.listProjects()
    }
  }
}

const root = new Container()
  .declareScopeInputs<{ feature: FeatureContext }>()
  .registerClass('api', ApiClient, [])
  .registerClass('projectsVm', ProjectsViewModel, ['feature', 'api'], 'scoped')

type AppContainer = typeof root
type PageContainer = ReturnType<typeof createProjectsPageScope>

function createProjectsPageScope(parent: AppContainer) {
  return parent.createScope({
    feature: { featureName: 'projects' }
  })
}

const AppDI = inferdiReact<AppContainer>()
const PageDI = AppDI.createScope({
  createScope: (parent): PageContainer => createProjectsPageScope(parent),
  onDisposeError: (error) => console.error(error)
})

export function AppDIProvider({ children }: PropsWithChildren) {
  return <AppDI.Provider container={root}>{children}</AppDI.Provider>
}

export function ProjectsPage({ children }: PropsWithChildren) {
  return <PageDI.ScopeProvider>{children}</PageDI.ScopeProvider>
}

export function useProjectsViewModel() {
  return PageDI.useService('projectsVm')
}

Datei im Repository: examples/frontend/react.tsx

React Native

tsx
import { Container } from '@inferdi/inferdi'
import {
  createContext,
  type PropsWithChildren,
  useContext,
  useEffect,
  useState
} from 'react'

type ScreenContext = {
  readonly screenName: string
}

class DeviceStorage {
  async getItem(key: string) {
    return `value:${key}`
  }
}

class SettingsViewModel {
  constructor(
    private readonly screen: ScreenContext,
    private readonly storage: DeviceStorage
  ) {}

  async loadTheme() {
    return {
      screen: this.screen.screenName,
      theme: await this.storage.getItem('theme')
    }
  }
}

const root = new Container()
  .declareScopeInputs<{ screen: ScreenContext }>()
  .registerClass('storage', DeviceStorage, [])
  .registerClass('settingsVm', SettingsViewModel, ['screen', 'storage'], 'scoped')

type AppContainer = typeof root
type ScreenContainer = ReturnType<typeof createSettingsScreenScope>

function createSettingsScreenScope(parent: AppContainer) {
  return parent.createScope({
    screen: { screenName: 'settings' }
  })
}

const RootDIContext = createContext<AppContainer | null>(null)
const ScreenDIContext = createContext<ScreenContainer | null>(null)

export function AppDIProvider({ children }: PropsWithChildren) {
  return <RootDIContext.Provider value={root}>{children}</RootDIContext.Provider>
}

export function SettingsScreenScope({ children }: PropsWithChildren) {
  const parent = useContext(RootDIContext)
  if (parent === null) throw new Error('DI provider is missing')

  const [scope, setScope] = useState<ScreenContainer | null>(null)

  useEffect(() => {
    const nextScope = createSettingsScreenScope(parent)
    setScope(nextScope)

    return () => {
      nextScope.dispose().catch(console.error)
    }
  }, [parent])

  if (scope === null) return null

  return <ScreenDIContext.Provider value={scope}>{children}</ScreenDIContext.Provider>
}

export function useSettingsViewModel() {
  const container = useContext(ScreenDIContext)
  if (container === null) throw new Error('Settings screen scope is missing')
  return container.get('settingsVm')
}

Datei im Repository: examples/frontend/react-native.tsx

Vue

ts
import { Container } from '@inferdi/inferdi'
import {
  defineComponent,
  inject,
  onUnmounted,
  provide,
  type InjectionKey
} from 'vue'

type RouteContext = {
  readonly routeName: string
}

class ApiClient {
  async dashboard() {
    return { total: 42 }
  }
}

class DashboardViewModel {
  constructor(
    private readonly route: RouteContext,
    private readonly api: ApiClient
  ) {}

  async load() {
    return {
      route: this.route.routeName,
      dashboard: await this.api.dashboard()
    }
  }
}

const root = new Container()
  .declareScopeInputs<{ route: RouteContext }>()
  .registerClass('api', ApiClient, [])
  .registerClass('dashboardVm', DashboardViewModel, ['route', 'api'], 'scoped')

type AppContainer = typeof root
type RouteContainer = ReturnType<typeof createDashboardRouteScope>

function createDashboardRouteScope(parent: AppContainer) {
  return parent.createScope({ route: { routeName: 'dashboard' } })
}

const RootDIKey: InjectionKey<AppContainer> = Symbol('InferDI.root')
const RouteDIKey: InjectionKey<RouteContainer> = Symbol('InferDI.route')

export const AppDIProvider = defineComponent({
  setup(_props, { slots }) {
    provide(RootDIKey, root)
    return () => slots.default?.()
  }
})

export const DashboardRouteProvider = defineComponent({
  setup(_props, { slots }) {
    const parent = inject(RootDIKey)
    if (parent === undefined) throw new Error('DI provider is missing')

    const scope = createDashboardRouteScope(parent)
    provide(RouteDIKey, scope)

    // Vue unmount hooks are synchronous; call async dispose explicitly
    onUnmounted(() => {
      scope.dispose().catch(console.error)
    })

    return () => slots.default?.()
  }
})

export function useDashboardViewModel() {
  const container = inject(RouteDIKey)
  if (container === undefined) throw new Error('Dashboard route scope is missing')
  return container.get('dashboardVm')
}

Datei im Repository: examples/frontend/vue.ts

Svelte

ts
import { Container } from '@inferdi/inferdi'
import { getContext, onDestroy, setContext } from 'svelte'

type RouteContext = {
  readonly routeName: string
}

class ApiClient {
  async loadInbox() {
    return [{ id: 'msg_1', subject: 'Hello' }]
  }
}

class InboxViewModel {
  constructor(
    private readonly route: RouteContext,
    private readonly api: ApiClient
  ) {}

  async load() {
    return {
      route: this.route.routeName,
      messages: await this.api.loadInbox()
    }
  }
}

const root = new Container()
  .declareScopeInputs<{ route: RouteContext }>()
  .registerClass('api', ApiClient, [])
  .registerClass('inboxVm', InboxViewModel, ['route', 'api'], 'scoped')

type AppContainer = typeof root
type RouteContainer = ReturnType<typeof createInboxRouteScope>

const RootDIKey = Symbol('InferDI.root')
const RouteDIKey = Symbol('InferDI.route')

function createInboxRouteScope(parent: AppContainer) {
  return parent.createScope({ route: { routeName: 'inbox' } })
}

export function provideRootContainer() {
  setContext(RootDIKey, root)
}

export function provideInboxRouteScope() {
  const parent = getContext<AppContainer | undefined>(RootDIKey)
  if (parent === undefined) {
    throw new Error('InferDI root context is missing — call provideRootContainer() in a parent layout')
  }
  const scope = createInboxRouteScope(parent)

  setContext(RouteDIKey, scope)

  // Svelte onDestroy is synchronous; start async cleanup and report failures
  onDestroy(() => {
    scope.dispose().catch(console.error)
  })
}

export function getInboxViewModel() {
  const scope = getContext<RouteContainer | undefined>(RouteDIKey)
  if (scope === undefined) {
    throw new Error('Inbox route scope is missing — call provideInboxRouteScope() in this route')
  }
  return scope.get('inboxVm')
}

Datei im Repository: examples/frontend/svelte.ts