@v4fire/client
Version:
V4Fire client core library
344 lines (275 loc) • 7.83 kB
text/typescript
/*!
* V4Fire Client Core
* https://github.com/V4Fire/Client
*
* Released under the MIT license
* https://github.com/V4Fire/Client/blob/master/LICENSE
*/
/**
* [[include:core/component/field/README.md]]
* @packageDocumentation
*/
import { defProp } from 'core/const/props';
import { fieldQueue } from 'core/component/field/const';
import type { ComponentInterface, ComponentField } from 'core/component/interface';
export * from 'core/component/field/const';
/**
* Initializes the specified fields to a component instance.
* The function returns an object with initialized fields.
*
* This method has some "copy-paste" chunks, but it's done for better performance because it's a very "hot" function.
* Mind that the initialization of fields is a synchronous operation.
*
* @param fields - component fields or system fields
* @param component - component instance
* @param [store] - storage object for initialized fields
*/
// eslint-disable-next-line complexity
export function initFields(
fields: Dictionary<ComponentField>,
component: ComponentInterface,
store: Dictionary = {}
): Dictionary {
const {
unsafe,
unsafe: {meta: {componentName, params, instance}},
isFlyweight
} = component;
const
ssrMode = component.$renderEngine.supports.ssr,
isNotRegular = params.functional === true || isFlyweight;
const
// A map of fields that we should skip, i.e., not to initialize.
// For instance, some properties don't initialize if a component is a functional.
canSkip = Object.createDict(),
// List of atoms to initialize
atomList = <Array<Nullable<string>>>[],
// List of non-atoms to initialize
nonAtomList = <Array<Nullable<string>>>[];
const
NULL = {};
const defField = {
...defProp,
value: NULL
};
// At first, we should initialize all atoms, but some atoms wait for other atoms.
// That's why we sort the source list of fields and organize a simple synchronous queue.
// All atoms that wait for other atoms are added to `atomList`.
// All non-atoms are added to `nonAtomList`.
for (let keys = Object.keys(fields).sort(), i = 0; i < keys.length; i++) {
const
key = keys[i],
el = fields[key];
let
sourceVal = store[key],
isNull = false;
if (isFlyweight) {
if (
el != null && (
el.replace !== true && (Object.isTruly(el.unique) || el.src === componentName) ||
el.replace === false
)
) {
Object.defineProperty(store, key, defField);
sourceVal = undefined;
isNull = true;
}
}
const dontNeedInit =
el == null ||
sourceVal !== undefined ||
// Don't initialize a property for the functional component unless explicitly required
!ssrMode && isNotRegular && el.functional === false ||
el.init == null && el.default === undefined && instance[key] === undefined;
if (el == null || dontNeedInit) {
canSkip[key] = true;
store[key] = sourceVal;
continue;
}
if (el.atom) {
// If true, then the field does not have any dependencies and can be initialized right now
let canInit = true;
const
{after} = el;
if (after && after.size > 0) {
for (let o = after.values(), val = o.next(); !val.done; val = o.next()) {
const
waitKey = val.value;
if (canSkip[waitKey] === true) {
continue;
}
// Check the dependency is not already initialized
if (!(waitKey in store)) {
atomList.push(key);
canInit = false;
break;
}
}
}
if (canInit) {
if (isNull) {
store[key] = undefined;
}
// @ts-ignore (access)
unsafe['$activeField'] = key;
let
val;
if (el.init != null) {
val = el.init(unsafe, store);
}
if (val === undefined) {
if (store[key] === undefined) {
// We need to clone the default value from a constructor
// to prevent linking to the same object for a non-primitive value
val = el.default !== undefined ? el.default : Object.fastClone(instance[key]);
store[key] = val;
}
} else {
store[key] = val;
}
// @ts-ignore (access)
unsafe['$activeField'] = undefined;
}
} else {
nonAtomList.push(key);
}
}
// Initialize all atoms that have some dependencies
while (atomList.length > 0) {
for (let i = 0; i < atomList.length; i++) {
const
key = nonAtomList[i],
el = key != null ? fields[key] : null;
let
isNull = false;
if (el == null || key == null || key in store && !(isNull = store[key] === NULL)) {
continue;
}
// If true, then the field does not have any dependencies and can be initialized right now
let canInit = true;
const
{after} = el;
if (after && after.size > 0) {
for (let o = after.values(), val = o.next(); !val.done; val = o.next()) {
const
waitKey = val.value,
waitFor = fields[waitKey];
if (canSkip[waitKey] === true) {
continue;
}
if (!waitFor) {
throw new ReferenceError(`The field "${waitKey}" is not defined`);
}
if (!waitFor.atom) {
throw new Error(`The atom field "${key}" can't wait the non atom field "${waitKey}"`);
}
if (!(waitKey in store)) {
fieldQueue.add(key);
canInit = false;
break;
}
}
if (canInit) {
atomList[i] = null;
}
}
if (canInit) {
if (isNull) {
store[key] = undefined;
}
// @ts-ignore (access)
unsafe['$activeField'] = key;
fieldQueue.delete(key);
let
val;
if (el.init != null) {
val = el.init(unsafe, store);
}
if (val === undefined) {
if (store[key] === undefined) {
// We need to clone the default value from a constructor
// to prevent linking to the same object for a non-primitive value
val = el.default !== undefined ? el.default : Object.fastClone(instance[key]);
store[key] = val;
}
} else {
store[key] = val;
}
// @ts-ignore (access)
unsafe['$activeField'] = undefined;
}
}
// All atoms are initialized
if (fieldQueue.size === 0) {
break;
}
}
// Initialize all non-atoms
while (nonAtomList.length > 0) {
for (let i = 0; i < nonAtomList.length; i++) {
const
key = nonAtomList[i],
el = key != null ? fields[key] : null;
let
isNull = false;
if (el == null || key == null || key in store && !(isNull = store[key] === NULL)) {
continue;
}
// If true, then the field does not have any dependencies and can be initialized right now
let canInit = true;
const
{after} = el;
if (after && after.size > 0) {
for (let o = after.values(), val = o.next(); !val.done; val = o.next()) {
const
waitKey = val.value,
waitFor = fields[waitKey];
if (canSkip[waitKey] === true) {
continue;
}
if (!waitFor) {
throw new ReferenceError(`The field "${waitKey}" is not defined`);
}
if (!(waitKey in store)) {
fieldQueue.add(key);
canInit = false;
break;
}
}
if (canInit) {
nonAtomList[i] = null;
}
}
if (canInit) {
if (isNull) {
store[key] = undefined;
}
// @ts-ignore (access)
unsafe['$activeField'] = key;
fieldQueue.delete(key);
let
val;
if (el.init != null) {
val = el.init(unsafe, store);
}
if (val === undefined) {
if (store[key] === undefined) {
// We need to clone the default value from a constructor
// to prevent linking to the same object for a non-primitive value
val = el.default !== undefined ? el.default : Object.fastClone(instance[key]);
store[key] = val;
}
} else {
store[key] = val;
}
// @ts-ignore (access)
unsafe['$activeField'] = undefined;
}
}
// All fields are initialized
if (fieldQueue.size === 0) {
break;
}
}
return store;
}