@cfcs/core
Version:
Write once, create framework components that supports React, Vue, Svelte, and more.
196 lines (174 loc) • 6.03 kB
text/typescript
/**
* cfcs
* Copyright (c) 2022-present NAVER Corp.
* MIT license
*/
import { isFunction, keys } from "../core";
import { Ref } from "../core/types";
import { ReactiveAdapter, ReactiveAdapterParam } from "./ReactiveAdapter";
import { ReactiveSubscribe } from "./decorators/ReactiveSubscribe";
import { ReactiveEventCallback, ReactiveState } from "./types";
import { getObservers, withReactiveMethods } from "./utils";
import Component from "@egjs/component";
import { EventKey, EventTriggerParams } from "@egjs/component/declaration/types";
/**
* @category Reactive
* @hidden
*/
export function adaptReactive<
Instance extends ReactiveSubscribe<Record<string, any>>,
State extends Record<string, any> = ReactiveState<Instance>,
Methods extends keyof Partial<Instance> = never,
Props = any,
Events extends Record<string, any> = {},
>(adapter: ReactiveAdapterParam<Instance, State, Methods, Props, Events>, props?: () => Props) {
const objectAdapter: ReactiveAdapter<Instance, State, Methods, Props, Events> = isFunction(adapter) ? {
setup: adapter,
} : adapter;
function getProps(): Props {
return props?.() ?? objectAdapter.props?.() ?? objectAdapter.data?.() ?? {} as Props;
}
const eventEmitter = new Component<Events>();
const mountedHooks: Array<(props: Props, instance?: Instance | null) => Instance | void> = [];
const initHooks: Array<(instance: Instance, props: Props) => void> = [];
const destroyHooks: Array<(instance: Instance, props: Props) => void> = [];
const onHooks: Array<<EventName extends EventKey<Events>>(instance: Instance, eventName: EventName, listener: ReactiveEventCallback<Events, EventName>) => void | (() => void)> = [];
const instanceRef: Ref<Instance> = { current: null };
let offHooksList: Array<Array<() => void>> = [];
let initialState: State | null = null;
let eventNames: readonly (keyof Events)[] = [];
let methodNames: readonly Methods[] = [];
const onMounted = (callback: (props: Props, instance?: Instance | null) => Instance | void) => {
mountedHooks.push(callback);
};
const onInit = (callback: (instance: Instance, props: Props) => void) => {
initHooks.push(callback);
};
const onDestroy = (callback: (instance: Instance, props: Props) => void): void => {
destroyHooks.push(callback)
};
const on = (callback: <EventName extends EventKey<Events>>(instance: Instance, eventName: EventName, listener: ReactiveEventCallback<Events, EventName>) => void | (() => void)) => {
onHooks.push(callback);
};
const emit = <EventName extends EventKey<Events>>(eventName: EventName, ...params: EventTriggerParams<Events, EventName>) => {
eventEmitter.trigger(eventName, ...params);
};
const setInitialState = (state: State) => {
initialState = state;
};
const setEvents = (events: readonly (keyof Events)[]) => {
eventNames = events;
}
const setMethods = (methods: readonly Methods[]) => {
methodNames = methods;
}
if (objectAdapter.setup) {
instanceRef.current = objectAdapter.setup({
getProps,
setInitialState,
setEvents,
setMethods,
onMounted,
onDestroy,
onInit,
emit,
on,
}) || null;
}
if (objectAdapter.created) {
instanceRef.current = objectAdapter.created(getProps()) || null;
}
if (objectAdapter.events) {
setEvents(objectAdapter.events);
}
if (objectAdapter.state) {
setInitialState(objectAdapter.state);
}
if (objectAdapter.methods) {
setMethods(objectAdapter.methods);
}
if (objectAdapter.mounted) {
onMounted(objectAdapter.mounted);
}
if (objectAdapter.destroy) {
destroyHooks.push(objectAdapter.destroy);
}
if (objectAdapter.init) {
initHooks.push(objectAdapter.init);
}
if (objectAdapter.on) {
onHooks.push((instance, eventName, listener) => {
const off = objectAdapter.on!(instance, eventName, listener);
return () => {
off && off();
objectAdapter.off?.(instance, eventName, listener);
};
});
}
return {
events: () => eventNames,
state(): State {
const inst = instanceRef.current;
if (initialState) {
return initialState;
}
if (inst) {
const observers = getObservers(inst);
setInitialState(keys(observers).reduce((prev, cur) => {
prev[cur] = observers[cur].current;
return prev;
}, {} as any));
}
return initialState || {} as State;
},
instance() {
return instanceRef.current;
},
mounted(): void {
const props = getProps();
mountedHooks.forEach(hook => {
instanceRef.current = hook(props, instanceRef.current) || instanceRef.current;
});
},
init(): void {
// on events
const instance = instanceRef.current!;
const props = getProps();
offHooksList = (eventNames as string[]).map(eventName => {
const listener = (...params: any[]) => {
(eventEmitter as any).trigger(eventName, ...params);
};
const instance = instanceRef.current!;
return onHooks.map(hook => hook(instance, eventName, listener as any)).filter(Boolean) as Array<() => void>;
});
// init
initHooks.forEach(hook => {
hook(instance, props);
});
},
destroy(): void {
// off events
offHooksList.forEach(offHooks => {
offHooks.forEach(hook => {
hook();
});
});
// destroy
eventEmitter.off();
const instance = instanceRef.current!;
const props = getProps();
destroyHooks.forEach(hook => {
hook(instance, props);
});
},
methods() {
return withReactiveMethods<any, any, any>(instanceRef, methodNames);
},
on(eventName: string, listener: ReactiveEventCallback<any, any>) {
eventEmitter.on(eventName, listener);
},
off(eventName: string, listener: ReactiveEventCallback<any, any>) {
eventEmitter.off(eventName, listener);
},
};
}