fix: enhance readability by replacing forEach with for-of loops in rendering logic
This commit is contained in:
parent
75a37043b7
commit
aae4a85a49
101
src/main.ts
101
src/main.ts
|
@ -447,12 +447,11 @@ export default (options: ComponentOptions) => {
|
||||||
this._evaluateIfCondition(element, expr)
|
this._evaluateIfCondition(element, expr)
|
||||||
|
|
||||||
const statePaths = this._extractStatePathsFromExpression(expr)
|
const statePaths = this._extractStatePathsFromExpression(expr)
|
||||||
statePaths.forEach((path) => {
|
for (const path of statePaths) {
|
||||||
if (!this._stateToElementsMap[path]) {
|
if (!this._stateToElementsMap[path])
|
||||||
this._stateToElementsMap[path] = new Set()
|
this._stateToElementsMap[path] = new Set()
|
||||||
}
|
|
||||||
this._stateToElementsMap[path].add(element as HTMLElement)
|
this._stateToElementsMap[path].add(element as HTMLElement)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle list rendering (%for macro)
|
// Handle list rendering (%for macro)
|
||||||
|
@ -504,26 +503,23 @@ export default (options: ComponentOptions) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detach all currently rendered DOM items managed by this instance.
|
// Detach all currently rendered DOM items managed by this instance.
|
||||||
renderedItems.forEach((item) => {
|
for (const item of renderedItems)
|
||||||
if (item.element.parentNode === parentNode) {
|
if (item.element.parentNode === parentNode)
|
||||||
parentNode.removeChild(item.element)
|
parentNode.removeChild(item.element)
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get key attribute if available
|
// Get key attribute if available
|
||||||
const keyAttr = template.getAttribute('%key')
|
const keyAttr = template.getAttribute('%key')
|
||||||
if (!keyAttr)
|
if (!keyAttr)
|
||||||
console.warn(
|
console.warn(
|
||||||
`%key attribute not found in the template, which is not a recommended practice.`,
|
'%key attribute not found in the template, which is not a recommended practice.'
|
||||||
)
|
)
|
||||||
|
|
||||||
// Store a map of existing items by key for reuse
|
// Store a map of existing items by key for reuse
|
||||||
const existingElementsByKey = new Map()
|
const existingElementsByKey = new Map()
|
||||||
renderedItems.forEach((item) => {
|
// renderedItems.forEach((item) => {
|
||||||
if (item.key !== undefined) {
|
for (const item of renderedItems)
|
||||||
|
if (item.key !== undefined)
|
||||||
existingElementsByKey.set(item.key, item)
|
existingElementsByKey.set(item.key, item)
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Clear rendered items
|
// Clear rendered items
|
||||||
renderedItems.length = 0
|
renderedItems.length = 0
|
||||||
|
@ -595,20 +591,19 @@ export default (options: ComponentOptions) => {
|
||||||
placeholder.parentNode?.insertBefore(fragment, placeholder.nextSibling)
|
placeholder.parentNode?.insertBefore(fragment, placeholder.nextSibling)
|
||||||
|
|
||||||
// Remove any remaining unused items
|
// Remove any remaining unused items
|
||||||
existingElementsByKey.forEach((item) => {
|
// existingElementsByKey.forEach((item) => {
|
||||||
if (item.element.parentNode) {
|
for (const item of existingElementsByKey.values())
|
||||||
|
if (item.element.parentNode)
|
||||||
item.element.parentNode.removeChild(item.element)
|
item.element.parentNode.removeChild(item.element)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initial render
|
// Initial render
|
||||||
updateList()
|
updateList()
|
||||||
|
|
||||||
// Set up state dependency for collection changes
|
// Set up state dependency for collection changes
|
||||||
if (!this._stateToElementsMap[collectionExpr]) {
|
if (!this._stateToElementsMap[collectionExpr])
|
||||||
this._stateToElementsMap[collectionExpr] = new Set()
|
this._stateToElementsMap[collectionExpr] = new Set()
|
||||||
}
|
|
||||||
// Using a unique identifier for this list rendering instance
|
// Using a unique identifier for this list rendering instance
|
||||||
const listVirtualElement = document.createElement('div')
|
const listVirtualElement = document.createElement('div')
|
||||||
this._stateToElementsMap[collectionExpr].add(
|
this._stateToElementsMap[collectionExpr].add(
|
||||||
|
@ -651,14 +646,14 @@ export default (options: ComponentOptions) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the text nodes of the element itself
|
// Process the text nodes of the element itself
|
||||||
Array.from(element.childNodes).forEach((node) => {
|
// Array.from(element.childNodes).forEach((node) => {
|
||||||
if (node.nodeType === Node.TEXT_NODE) {
|
for (const node of Array.from(element.childNodes))
|
||||||
|
if (node.nodeType === Node.TEXT_NODE)
|
||||||
processTextNodes(node)
|
processTextNodes(node)
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 3. Process attribute bindings (:attr)
|
// 3. Process attribute bindings (:attr)
|
||||||
Array.from(element.attributes).forEach((attr) => {
|
// Array.from(element.attributes).forEach((attr) => {
|
||||||
|
for (const attr of Array.from(element.attributes)) {
|
||||||
if (attr.name.startsWith(':')) {
|
if (attr.name.startsWith(':')) {
|
||||||
const attrName = attr.name.substring(1)
|
const attrName = attr.name.substring(1)
|
||||||
const expr = attr.value.trim()
|
const expr = attr.value.trim()
|
||||||
|
@ -667,17 +662,17 @@ export default (options: ComponentOptions) => {
|
||||||
itemContext,
|
itemContext,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (value !== undefined) {
|
if (value !== undefined)
|
||||||
element.setAttribute(attrName, String(value))
|
element.setAttribute(attrName, String(value))
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the original binding attribute (execute only for cloned templates once)
|
// Remove the original binding attribute (execute only for cloned templates once)
|
||||||
element.removeAttribute(attr.name)
|
element.removeAttribute(attr.name)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
// 4. Process event bindings (@event)
|
// 4. Process event bindings (@event)
|
||||||
Array.from(element.attributes).forEach((attr) => {
|
// Array.from(element.attributes).forEach((attr) => {
|
||||||
|
for (const attr of Array.from(element.attributes)) {
|
||||||
if (attr.name.startsWith('@')) {
|
if (attr.name.startsWith('@')) {
|
||||||
const eventName = attr.name.substring(1)
|
const eventName = attr.name.substring(1)
|
||||||
const handlerValue = attr.value.trim()
|
const handlerValue = attr.value.trim()
|
||||||
|
@ -707,13 +702,14 @@ export default (options: ComponentOptions) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
// 5. Process conditional rendering (%if)
|
// 5. Process conditional rendering (%if)
|
||||||
let isConditional = false
|
let isConditional = false
|
||||||
let shouldDisplay = true
|
let shouldDisplay = true
|
||||||
|
|
||||||
Array.from(element.attributes).forEach((attr) => {
|
// Array.from(element.attributes).forEach((attr) => {
|
||||||
|
for (const attr of Array.from(element.attributes)) {
|
||||||
if (attr.name === '%if') {
|
if (attr.name === '%if') {
|
||||||
isConditional = true
|
isConditional = true
|
||||||
const expr = attr.value.trim()
|
const expr = attr.value.trim()
|
||||||
|
@ -729,9 +725,10 @@ export default (options: ComponentOptions) => {
|
||||||
shouldDisplay = Boolean(result)
|
shouldDisplay = Boolean(result)
|
||||||
|
|
||||||
// Apply the condition (in the list item context, we use display style to simplify)
|
// Apply the condition (in the list item context, we use display style to simplify)
|
||||||
if (!shouldDisplay) (element as HTMLElement).style.display = 'none'
|
if (!shouldDisplay)
|
||||||
|
(element as HTMLElement).style.display = 'none'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
// If the condition evaluates to false, skip further processing of this element
|
// If the condition evaluates to false, skip further processing of this element
|
||||||
if (isConditional && !shouldDisplay) {
|
if (isConditional && !shouldDisplay) {
|
||||||
|
@ -741,7 +738,8 @@ export default (options: ComponentOptions) => {
|
||||||
// 6. Process nested list rendering (%for)
|
// 6. Process nested list rendering (%for)
|
||||||
let hasForDirective = false
|
let hasForDirective = false
|
||||||
|
|
||||||
Array.from(element.attributes).forEach((attr) => {
|
// Array.from(element.attributes).forEach((attr) => {
|
||||||
|
for (const attr of Array.from(element.attributes)) {
|
||||||
if (attr.name === '%for') {
|
if (attr.name === '%for') {
|
||||||
hasForDirective = true
|
hasForDirective = true
|
||||||
const forExpr = attr.value.trim()
|
const forExpr = attr.value.trim()
|
||||||
|
@ -753,17 +751,16 @@ export default (options: ComponentOptions) => {
|
||||||
// Note: We need to evaluate the collection expression through the current item context here
|
// Note: We need to evaluate the collection expression through the current item context here
|
||||||
this._setupNestedListRendering(element, forExpr, itemContext)
|
this._setupNestedListRendering(element, forExpr, itemContext)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
// If this element is a list element, skip child element processing (they will be processed by the list processor)
|
|
||||||
if (hasForDirective) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this element is a list element, skip child element processing (they will be processed by the list processor)
|
||||||
|
if (hasForDirective)
|
||||||
|
return
|
||||||
|
|
||||||
// 7. Recursively process all child elements
|
// 7. Recursively process all child elements
|
||||||
Array.from(element.children).forEach((child) => {
|
// Array.from(element.children).forEach((child) => {
|
||||||
|
for (const child of Array.from(element.children))
|
||||||
this._processElementWithItemContext(child, itemContext)
|
this._processElementWithItemContext(child, itemContext)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up nested list rendering
|
// Set up nested list rendering
|
||||||
|
@ -851,15 +848,14 @@ export default (options: ComponentOptions) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the expression is an item property path
|
// Check if the expression is an item property path
|
||||||
if (itemVar && expression.startsWith(itemVar + '.')) {
|
if (itemVar && expression.startsWith(`${itemVar}.`)) {
|
||||||
const propertyPath = expression.substring(itemVar.length + 1)
|
const propertyPath = expression.substring(itemVar.length + 1)
|
||||||
const parts = propertyPath.split('.')
|
const parts = propertyPath.split('.')
|
||||||
let value = itemContext[itemVar]
|
let value = itemContext[itemVar]
|
||||||
|
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
if (value === undefined || value === null) {
|
if (value === undefined || value === null)
|
||||||
return undefined
|
return undefined
|
||||||
}
|
|
||||||
value = value[part]
|
value = value[part]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -959,11 +955,9 @@ export default (options: ComponentOptions) => {
|
||||||
throw new Error(`Invalid arrow function syntax: ${handlerValue}`)
|
throw new Error(`Invalid arrow function syntax: ${handlerValue}`)
|
||||||
}
|
}
|
||||||
const paramsStr = (() => {
|
const paramsStr = (() => {
|
||||||
if (splitted[0].includes('(')) {
|
if (splitted[0].includes('('))
|
||||||
return splitted[0].trim()
|
return splitted[0].trim()
|
||||||
} else {
|
|
||||||
return `(${splitted[0].trim()})`
|
return `(${splitted[0].trim()})`
|
||||||
}
|
|
||||||
})()
|
})()
|
||||||
const bodyStr = splitted[1].trim()
|
const bodyStr = splitted[1].trim()
|
||||||
|
|
||||||
|
@ -1033,16 +1027,14 @@ export default (options: ComponentOptions) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add all methods of the component
|
// Add all methods of the component
|
||||||
Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach(
|
// Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach(
|
||||||
(name) => {
|
// (name) => {
|
||||||
|
for (const name of Object.getOwnPropertyNames(Object.getPrototypeOf(this)))
|
||||||
if (
|
if (
|
||||||
typeof (this as any)[name] === 'function' &&
|
typeof (this as any)[name] === 'function' &&
|
||||||
name !== 'constructor'
|
name !== 'constructor'
|
||||||
) {
|
|
||||||
context[name] = (this as any)[name].bind(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
context[name] = (this as any)[name].bind(this)
|
||||||
|
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
@ -1198,9 +1190,8 @@ export default (options: ComponentOptions) => {
|
||||||
const parts = keyPath.split('.')
|
const parts = keyPath.split('.')
|
||||||
let result = this._states
|
let result = this._states
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
if (result === undefined || result === null) {
|
if (result === undefined || result === null)
|
||||||
return undefined
|
return undefined
|
||||||
}
|
|
||||||
result = result[part]
|
result = result[part]
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
@ -1208,7 +1199,7 @@ export default (options: ComponentOptions) => {
|
||||||
|
|
||||||
// function trigger
|
// function trigger
|
||||||
triggerFunc(eventName: string, ...args: any[]) {
|
triggerFunc(eventName: string, ...args: any[]) {
|
||||||
if (funcs && funcs[eventName]) funcs[eventName].call(this, ...args)
|
funcs?.[eventName]?.call(this, ...args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user