UNPKG

@v4fire/client

Version:

V4Fire client core library

416 lines (335 loc) • 10.6 kB
/*! * V4Fire Client Core * https://github.com/V4Fire/Client * * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ /** * [[include:core/component/construct/README.md]] * @packageDocumentation */ import symbolGenerator from 'core/symbol'; import { deprecate } from 'core/functools/deprecation'; import { unmute } from 'core/object/watch'; import Async from 'core/async'; import { asyncLabel } from 'core/component/const'; import { getPropertyInfo, PropertyInfo } from 'core/component/reflection'; import { initFields } from 'core/component/field'; import { attachAccessorsFromMeta } from 'core/component/accessor'; import { attachMethodsFromMeta, callMethodFromComponent } from 'core/component/method'; import { implementEventAPI } from 'core/component/event'; import { bindRemoteWatchers, implementComponentWatchAPI } from 'core/component/watch'; import { runHook } from 'core/component/hook'; import { resolveRefs } from 'core/component/ref'; import { getNormalParent } from 'core/component/traverse'; import { forkMeta } from 'core/component/meta'; import type { ComponentInterface, ComponentMeta, Hook } from 'core/component/interface'; import type { InitBeforeCreateStateOptions, InitBeforeDataCreateStateOptions } from 'core/component/construct/interface'; export * from 'core/component/construct/interface'; export const $$ = symbolGenerator(); /** * Initializes "beforeCreate" state to the specified component instance * * @param component * @param meta - component meta object * @param [opts] - additional options */ export function beforeCreateState( component: ComponentInterface, meta: ComponentMeta, opts?: InitBeforeCreateStateOptions ): void { meta = forkMeta(meta); // @ts-ignore (access) component.unsafe = component; // @ts-ignore (access) component.meta = meta; // @ts-ignore (access) component['componentName'] = meta.componentName; // @ts-ignore (access) component['instance'] = meta.instance; // @ts-ignore (access) component.$fields = {}; // @ts-ignore (access) component.$systemFields = {}; // @ts-ignore (access) component.$refHandlers = {}; // @ts-ignore (access) component.$modifiedFields = {}; // @ts-ignore (access) component.$async = new Async(component); // @ts-ignore (access) component.$asyncLabel = asyncLabel; const {unsafe, unsafe: {$parent: parent}} = component; const isFunctional = meta.params.functional === true; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (parent != null && parent.componentName == null) { // @ts-ignore (access) unsafe['$parent'] = unsafe.$root.unsafe.$remoteParent; } // @ts-ignore (access) unsafe['$normalParent'] = getNormalParent(component); if (opts?.addMethods) { attachMethodsFromMeta(component); } if (opts?.implementEventAPI) { implementEventAPI(component); } attachAccessorsFromMeta(component); runHook('beforeRuntime', component).catch(stderr); const { systemFields, tiedFields, computedFields, accessors, watchDependencies, watchers } = meta; initFields(systemFields, component, unsafe); const fakeHandler = () => undefined; if (watchDependencies.size > 0) { const watchSet = new Set<PropertyInfo>(); for (let o = watchDependencies.values(), el = o.next(); !el.done; el = o.next()) { const deps = el.value; for (let i = 0; i < deps.length; i++) { const dep = deps[i], info = getPropertyInfo(Object.isArray(dep) ? dep.join('.') : String(dep), component); if (info.type === 'system' || isFunctional && info.type === 'field') { watchSet.add(info); } } } // If a computed property a field or system field as a dependency, // and the host component does not have any watchers to this field, // we need to register the "fake" watcher to force watching if (watchSet.size > 0) { for (let o = watchSet.values(), el = o.next(); !el.done; el = o.next()) { const info = el.value; const needToForceWatching = watchers[info.name] == null && watchers[info.originalPath] == null && watchers[info.path] == null; if (needToForceWatching) { watchers[info.name] = [ { deep: true, immediate: true, provideArgs: false, handler: fakeHandler } ]; } } } } // If a computed property is tied with a field or system field // and the host component does not have any watchers to this field, // we need to register the "fake" watcher to force watching for (let keys = Object.keys(tiedFields), i = 0; i < keys.length; i++) { const key = keys[i], normalizedKey = tiedFields[key]; if (normalizedKey == null) { continue; } const needToForceWatching = watchers[key] == null && ( accessors[normalizedKey] != null || computedFields[normalizedKey] != null ); if (needToForceWatching) { watchers[key] = [ { deep: true, immediate: true, provideArgs: false, handler: fakeHandler } ]; } } runHook('beforeCreate', component).catch(stderr); callMethodFromComponent(component, 'beforeCreate'); } /** * Initializes "beforeDataCreate" state to the specified component instance * * @param component * @param [opts] - additional options */ export function beforeDataCreateState( component: ComponentInterface, opts?: InitBeforeDataCreateStateOptions ): void { const {meta, $fields} = component.unsafe; initFields(meta.fields, component, $fields); // Because in functional components, // the watching of fields can be initialized in a lazy mode if (meta.params.functional === true) { Object.assign(component, $fields); } Object.defineProperty(component, '$$data', { get(): typeof $fields { deprecate({name: '$$data', type: 'property', renamedTo: '$fields'}); return $fields; } }); runHook('beforeDataCreate', component) .catch(stderr); if (!component.isFlyweight && !component.$renderEngine.supports.ssr) { implementComponentWatchAPI(component, {tieFields: opts?.tieFields}); bindRemoteWatchers(component); } } /** * Initializes "created" state to the specified component instance * @param component */ export function createdState(component: ComponentInterface): void { const { unsafe, unsafe: {$root: r, $async: $a, $normalParent: parent} } = component; unmute(unsafe.$fields); unmute(unsafe.$systemFields); const isRegular = unsafe.meta.params.functional !== true && !unsafe.isFlyweight; if (parent != null && '$remoteParent' in r) { const p = parent.unsafe, onBeforeDestroy = unsafe.$destroy.bind(unsafe); p.$on('on-component-hook:before-destroy', onBeforeDestroy); $a.worker(() => p.$off('on-component-hook:before-destroy', onBeforeDestroy)); if (isRegular) { const activationHooks: Dictionary<boolean> = Object.createDict({ activated: true, deactivated: true }); const onActivation = (status: Hook) => { if (!activationHooks[status]) { return; } if (status === 'deactivated') { component.deactivate(); return; } $a.requestIdleCallback(component.activate.bind(component), { label: $$.remoteActivation, timeout: 50 }); }; if (activationHooks[p.hook]) { onActivation(p.hook); } p.$on('on-component-hook-change', onActivation); $a.worker(() => p.$off('on-component-hook-change', onActivation)); } } runHook('created', component).then(() => { callMethodFromComponent(component, 'created'); }).catch(stderr); } /** * Initializes "beforeMount" state to the specified component instance * @param component */ export function beforeMountState(component: ComponentInterface): void { const {$el} = component; if ($el != null) { $el.component = component; } if (!component.isFlyweight) { runHook('beforeMount', component).catch(stderr); callMethodFromComponent(component, 'beforeMount'); } } /** * Initializes "mounted" state to the specified component instance * @param component */ export function mountedState(component: ComponentInterface): void { const {$el} = component; if ($el != null && $el.component !== component) { $el.component = component; } resolveRefs(component); runHook('mounted', component).then(() => { callMethodFromComponent(component, 'mounted'); }).catch(stderr); } /** * Initializes "beforeUpdate" state to the specified component instance * @param component */ export function beforeUpdateState(component: ComponentInterface): void { runHook('beforeUpdate', component).catch(stderr); callMethodFromComponent(component, 'beforeUpdate'); } /** * Initializes "updated" state to the specified component instance * @param component */ export function updatedState(component: ComponentInterface): void { runHook('beforeUpdated', component).catch(stderr); resolveRefs(component); runHook('updated', component).then(() => { callMethodFromComponent(component, 'updated'); }).catch(stderr); } /** * Initializes "activated" state to the specified component instance * @param component */ export function activatedState(component: ComponentInterface): void { runHook('beforeActivated', component).catch(stderr); resolveRefs(component); runHook('activated', component).catch(stderr); callMethodFromComponent(component, 'activated'); } /** * Initializes "deactivated" state to the specified component instance * @param component */ export function deactivatedState(component: ComponentInterface): void { runHook('deactivated', component).catch(stderr); callMethodFromComponent(component, 'deactivated'); } /** * Initializes "beforeDestroy" state to the specified component instance * @param component */ export function beforeDestroyState(component: ComponentInterface): void { runHook('beforeDestroy', component).catch(stderr); callMethodFromComponent(component, 'beforeDestroy'); component.unsafe.$async.clearAll().locked = true; } /** * Initializes "destroyed" state to the specified component instance * @param component */ export function destroyedState(component: ComponentInterface): void { runHook('destroyed', component).then(() => { callMethodFromComponent(component, 'destroyed'); }).catch(stderr); } /** * Initializes "errorCaptured" state to the specified component instance * * @param component * @param args - additional arguments */ export function errorCapturedState(component: ComponentInterface, ...args: unknown[]): void { runHook('errorCaptured', component, ...args).then(() => { callMethodFromComponent(component, 'errorCaptured', ...args); }).catch(stderr); }