UNPKG

@cfcs/core

Version:

Write once, create framework components that supports React, Vue, Svelte, and more.

196 lines (174 loc) 6.03 kB
/** * 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); }, }; }