fix(infra): use framework stack provider (#10825)

Move the stack logic from react hook to FrameworkStackProvider,

now `frameworkProvider.get(ServerService)` is equal with `useService(ServerService)`
This commit is contained in:
EYHN
2025-03-13 08:56:05 +00:00
parent 7100d87efe
commit a903f8685b
3 changed files with 76 additions and 70 deletions

View File

@@ -7,5 +7,5 @@ export { createEvent, type FrameworkEvent, OnEvent } from './event';
export { Framework } from './framework'; export { Framework } from './framework';
export { createIdentifier } from './identifier'; export { createIdentifier } from './identifier';
export type { ResolveOptions } from './provider'; export type { ResolveOptions } from './provider';
export { FrameworkProvider } from './provider'; export { FrameworkProvider, FrameworkStackProvider } from './provider';
export type { GeneralIdentifier, Identifier } from './types'; export type { GeneralIdentifier, Identifier } from './types';

View File

@@ -322,3 +322,61 @@ export class BasicFrameworkProvider extends FrameworkProvider {
this.eventBus.dispose(); this.eventBus.dispose();
} }
} }
export class FrameworkStackProvider extends FrameworkProvider {
public readonly stack: FrameworkProvider[];
public readonly collection: Framework;
public readonly eventBus: EventBus;
constructor(providers: FrameworkProvider[]) {
if (providers.length === 0) {
throw new Error('FrameworkStackProvider must have at least one provider');
}
super();
this.stack = [...providers];
// use the collection and eventBus from the first provider
this.collection = this.stack[0].collection;
this.eventBus = this.stack[0].eventBus;
}
get scope(): FrameworkScopeStack {
return this.stack[0]?.scope || [];
}
getRaw(identifier: IdentifierValue, options?: ResolveOptions): any {
for (const provider of this.stack) {
const service = provider.getRaw(identifier, {
...options,
optional: true,
});
if (service) {
return service;
}
}
if (options?.optional) {
return undefined;
}
throw new ComponentNotFoundError(identifier);
}
getAllRaw(
identifier: IdentifierValue,
options?: ResolveOptions
): Map<ComponentVariant, any> {
for (const provider of this.stack) {
const components = provider.getAllRaw(identifier, options);
if (components.size > 0) {
return components;
}
}
return new Map();
}
dispose(): void {
// No need to handle the disposal of providers in the stack, as they are passed in externally
}
}

View File

@@ -1,43 +1,21 @@
import React, { useContext, useMemo } from 'react'; import React, { useContext, useMemo } from 'react';
import type { FrameworkProvider, Scope, Service } from '../core'; import type { FrameworkProvider, Scope, Service } from '../core';
import { ComponentNotFoundError, Framework } from '../core'; import { Framework, FrameworkStackProvider } from '../core';
import { parseIdentifier } from '../core/identifier';
import type { GeneralIdentifier, IdentifierType, Type } from '../core/types'; import type { GeneralIdentifier, IdentifierType, Type } from '../core/types';
export const FrameworkStackContext = React.createContext<FrameworkProvider[]>([ export const FrameworkProviderContext = React.createContext<FrameworkProvider>(
Framework.EMPTY.provider(), Framework.EMPTY.provider()
]); );
export function useFramework(): FrameworkProvider { export function useFramework(): FrameworkProvider {
const stack = useContext(FrameworkStackContext); return useContext(FrameworkProviderContext); // never null, because the default value
return stack[stack.length - 1]; // never null, because the default value
} }
export function useService<T extends Service>( export function useService<T extends Service>(
identifier: GeneralIdentifier<T> identifier: GeneralIdentifier<T>
): T { ): T {
// eslint-disable-next-line react-hooks/rules-of-hooks return useContext(FrameworkProviderContext).get(identifier);
const stack = useContext(FrameworkStackContext);
let service: T | undefined = undefined;
for (let i = stack.length - 1; i >= 0; i--) {
service = stack[i].getOptional(identifier, {
sameScope: true,
});
if (service) {
break;
}
}
if (!service) {
throw new ComponentNotFoundError(parseIdentifier(identifier));
}
return service;
} }
/** /**
@@ -57,27 +35,12 @@ export function useServices<
): keyof T extends string ): keyof T extends string
? { [key in Uncapitalize<keyof T>]: IdentifierType<T[Capitalize<key>]> } ? { [key in Uncapitalize<keyof T>]: IdentifierType<T[Capitalize<key>]> }
: never { : never {
const stack = useContext(FrameworkStackContext); const provider = useContext(FrameworkProviderContext);
const services: any = {}; const services: any = {};
for (const [key, value] of Object.entries(identifiers)) { for (const [key, value] of Object.entries(identifiers)) {
let service; services[key.charAt(0).toLowerCase() + key.slice(1)] = provider.get(value);
for (let i = stack.length - 1; i >= 0; i--) {
service = stack[i].getOptional(value, {
sameScope: true,
});
if (service) {
break;
}
}
if (!service) {
throw new ComponentNotFoundError(parseIdentifier(value));
}
services[key.charAt(0).toLowerCase() + key.slice(1)] = service;
} }
return services; return services;
@@ -86,22 +49,7 @@ export function useServices<
export function useServiceOptional<T extends Service>( export function useServiceOptional<T extends Service>(
identifier: Type<T> identifier: Type<T>
): T | undefined { ): T | undefined {
// eslint-disable-next-line react-hooks/rules-of-hooks return useContext(FrameworkProviderContext).getOptional(identifier);
const stack = useContext(FrameworkStackContext);
let service: T | undefined = undefined;
for (let i = stack.length - 1; i >= 0; i--) {
service = stack[i].getOptional(identifier, {
sameScope: true,
});
if (service) {
break;
}
}
return service;
} }
export const FrameworkRoot = ({ export const FrameworkRoot = ({
@@ -109,9 +57,9 @@ export const FrameworkRoot = ({
children, children,
}: React.PropsWithChildren<{ framework: FrameworkProvider }>) => { }: React.PropsWithChildren<{ framework: FrameworkProvider }>) => {
return ( return (
<FrameworkStackContext.Provider value={[framework]}> <FrameworkProviderContext.Provider value={framework}>
{children} {children}
</FrameworkStackContext.Provider> </FrameworkProviderContext.Provider>
); );
}; };
@@ -119,16 +67,16 @@ export const FrameworkScope = ({
scope, scope,
children, children,
}: React.PropsWithChildren<{ scope?: Scope }>) => { }: React.PropsWithChildren<{ scope?: Scope }>) => {
const stack = useContext(FrameworkStackContext); const provider = useContext(FrameworkProviderContext);
const nextStack = useMemo(() => { const nextStack = useMemo(() => {
if (!scope) return stack; if (!scope) return provider;
return [...stack, scope.framework]; return new FrameworkStackProvider([provider, scope.framework]);
}, [stack, scope]); }, [scope, provider]);
return ( return (
<FrameworkStackContext.Provider value={nextStack}> <FrameworkProviderContext.Provider value={nextStack}>
{children} {children}
</FrameworkStackContext.Provider> </FrameworkProviderContext.Provider>
); );
}; };