-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
⚡️ Group reactive tasks & skip the ones that are unnecessary
- Loading branch information
Showing
2 changed files
with
253 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,8 @@ | ||
const TASK_GROUP = Symbol('task_group'), | ||
APPLIED_OPTIONS = Hawkejs.APPLIED_OPTIONS, | ||
SCOPE_ID = Symbol('scope_id'), | ||
OPTIONS = Symbol('options'); | ||
OPTIONS = Symbol('options'), | ||
REACTIVE_QUEUE = Symbol('reactive_queue'); | ||
|
||
/** | ||
* The Renderer class | ||
|
@@ -78,6 +79,7 @@ const Renderer = Fn.inherits('Hawkejs.Base', function Renderer(hawkejs) { | |
this.active_variables = null; | ||
this.state = null; | ||
this.compiled_inlines = {}; | ||
this.reactive_queue = null; | ||
}); | ||
|
||
Renderer.setDeprecatedProperty('assign_end', 'assignEnd'); | ||
|
@@ -131,6 +133,33 @@ Renderer.setStatic(function enforceRootProperty(key, fnc) { | |
}); | ||
}); | ||
|
||
/** | ||
* Set a method that only executes on the root renderer | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 2.4.0 | ||
* @version 2.4.0 | ||
* | ||
* @param {String} key | ||
* @param {Function} fnc | ||
*/ | ||
Renderer.setStatic(function setRootMethod(key, fnc) { | ||
|
||
if (typeof key == 'function') { | ||
fnc = key; | ||
key = fnc.name; | ||
} | ||
|
||
this.setMethod(key, function rootMethod(...args) { | ||
|
||
if (!this.is_root_renderer) { | ||
return this.root_renderer[key](...args); | ||
} | ||
|
||
return fnc.call(this, ...args); | ||
}); | ||
}); | ||
|
||
/** | ||
* Set a reference to a specific singleton element | ||
* (html, head, body) | ||
|
@@ -176,7 +205,9 @@ Renderer.setStatic(function attachReactiveListeners(element, reactive) { | |
} | ||
|
||
if (reactive.body?.values?.length) { | ||
reactive.body.values.forEach(optional => optional.onChange(() => reactiveRerender(element))); | ||
reactive.body.values.forEach(optional => optional.onChange(() => { | ||
queueReactiveTask(element, 'body', () => reactiveRerender(element)); | ||
})); | ||
} | ||
|
||
if (reactive.attributes) { | ||
|
@@ -230,11 +261,32 @@ const attachReactiveElementUpdaters = (element, setter, getter, instructions) => | |
} | ||
|
||
config.values.forEach(optional => optional.onChange(() => { | ||
return updateElementProperty(element, setter, getter, optional, key, config) | ||
return queueReactiveTask(element, 'property', () => updateElementProperty(element, setter, getter, optional, key, config)); | ||
})); | ||
} | ||
}; | ||
|
||
/** | ||
* Queue the given task | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 2.4.0 | ||
* @version 2.4.0 | ||
* | ||
* @param {HTMLElement} element | ||
* @param {string} type | ||
* @param {Function} task | ||
*/ | ||
const queueReactiveTask = (element, type, task) => { | ||
|
||
// Make sure we have all the required info for a rerender | ||
if (type == 'body' && !element.is_custom_hawkejs_element && !element[Hawkejs.RENDER_INSTRUCTION]) { | ||
return; | ||
} | ||
|
||
return element.hawkejs_renderer.queueReactiveTask(element, type, task); | ||
}; | ||
|
||
/** | ||
* Perform the given reactive property update | ||
* | ||
|
@@ -942,6 +994,71 @@ Renderer.setMethod(function toDry() { | |
return result; | ||
}); | ||
|
||
/** | ||
* Queue a reactive task | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 2.4.0 | ||
* @version 2.4.0 | ||
* | ||
* @param {HTMLElement} element The element in question | ||
* @param {string} type What will be affected: property or body | ||
* @param {Function} task The actual task | ||
*/ | ||
Renderer.setRootMethod(function queueReactiveTask(element, type, task) { | ||
|
||
if (!this[REACTIVE_QUEUE]) { | ||
this[REACTIVE_QUEUE] = []; | ||
|
||
Blast.nextGroupedImmediate(() => { | ||
// Get the current queue | ||
let queue = this[REACTIVE_QUEUE]; | ||
|
||
// Remove the queue so new tasks can be added | ||
this[REACTIVE_QUEUE] = null; | ||
|
||
// Now we have to take a look at all the queued elements. | ||
// Any element that has a task queued while it's in another element | ||
// that will completely rerender its contents should be skipped | ||
let allowed_queue = [], | ||
elements_to_rerender = new Set(); | ||
|
||
// First pass: get all the elements to re-render | ||
for (let item of queue) { | ||
if (item.type == 'body') { | ||
elements_to_rerender.add(item.element); | ||
} | ||
} | ||
|
||
// Second pass: skip elements inside elements that will be re-rendered | ||
for (let item of queue) { | ||
let is_allowed = true; | ||
|
||
for (let ancestor of elements_to_rerender) { | ||
if (ancestor != item.element && ancestor.contains(item.element)) { | ||
is_allowed = false; | ||
break; | ||
} | ||
} | ||
|
||
if (is_allowed) { | ||
allowed_queue.push(item); | ||
} | ||
} | ||
|
||
for (let item of allowed_queue) { | ||
try { | ||
item.task.call(this, item.element); | ||
} catch (err) { | ||
console.error(err); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
this[REACTIVE_QUEUE].push({element, task, type}); | ||
}); | ||
|
||
/** | ||
* Convert to JSON | ||
* | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters