This commit is contained in:
2026-03-15 10:14:58 +09:00
parent 98c9ffbc89
commit 054f8b471d
9 changed files with 352 additions and 98 deletions

104
src/module/ComponentBase.ts Normal file
View File

@@ -0,0 +1,104 @@
import { State, type ValuesOfStateArray } from "./State";
export abstract class ComponentBase<Props extends PropsBase, States extends StatesBase> {
static propsToPropStates<Props extends PropsBase>(props: Props | PropStates<Props>) {
const propsState: Record<string, State<any>> = {};
Object.entries(props).forEach(([key, value]) => {
if (value instanceof State) {
propsState[key] = value;
}
else {
propsState[key] = new State(value);
}
});
return propsState as PropStates<Props>;
}
states: States = {} as States;
propStates: PropStates<Props>;
children: ComponentBase<any, any>[] = [];
mounted: boolean = false;
subscriberSymbols: { state: State<any>, symbol: Symbol }[] = [];
abstract node: Node;
constructor(props: PropStates<Props> | Props) {
this.propStates = ComponentBase.propsToPropStates(props);
this.init();
}
init(): void { }
protected abstract createNode(): Node;
mount(target: Node) {
if (this.mounted) {
throw new Error("This component is already mounted");
}
target.appendChild(this.node);
this.mounted = true;
this.onMount?.();
}
destroy() {
this.children.forEach((child) => child.destroy());
this.subscriberSymbols.forEach(({ state, symbol }) => {
state.unsubscribe(symbol);
});
this.onDestoy?.();
};
onMount(): void { };
onDestoy(): void { };
effect<const T extends State<any>[]>(callback: (...values: ValuesOfStateArray<T>) => void, states: T) {
states.forEach((state) => {
const symbol = state.subscribe(() => callback(...states.map(({ value }) => value) as ValuesOfStateArray<T>));
this.subscriberSymbols.push({ state, symbol });
})
}
derived<const T extends State<any>[], U>(callback: (...values: ValuesOfStateArray<T>) => U, states: T): State<U> {
const derivedState = new State<U>(undefined as U);
states.forEach((state) => {
const symbol = state.subscribe(() => {
const derivedValue = callback(...states.map(({ value }) => value) as ValuesOfStateArray<T>);
derivedState.set(derivedValue);
});
this.subscriberSymbols.push({ state, symbol });
});
return derivedState;
}
update<T extends (keyof Props | (string & {})), U>(
key: T,
value: T extends keyof Props ?
Props[T] extends State<U> ? U : Props[T] :
U
) {
let propState: State<any> = this.propStates[key];
if (!propState) {
propState = new State(value);
this.propStates[key] = propState;
}
propState.set(value);
}
}
export type StatesBase = Record<string, State<any>>;
export type PropsBase = Record<string, any>;
export type PropStates<Props extends PropsBase> = {
[K in keyof Props]: State<Props[K]>
}
export type ComponentBlueprint<Props extends PropsBase, States extends StatesBase> = {
type: 'component',
component: new (props: Props, children?: Blueprint[]) => ComponentBase<Props, States>,
props: Props,
children: Blueprint[]
}
export type HTMLBlueprint = {
type: 'html',
tag: string;
props: Record<string, any>;
children: Blueprint[]
}
export type TextBlueprint = {
type: 'text',
props: { content: State<any> | any }
}
export type Blueprint = ComponentBlueprint<any, any> | HTMLBlueprint | TextBlueprint;