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:
@@ -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';
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user