mirror of
https://github.com/jaandrle/deka-dom-el
synced 2025-04-04 12:45:54 +02:00
🔤
This commit is contained in:
parent
0a2d17ac6f
commit
8756dabc55
BIN
docs/assets/devtools.png
Normal file
BIN
docs/assets/devtools.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 145 KiB |
@ -1,5 +1,6 @@
|
||||
import { S } from "deka-dom-el/signals";
|
||||
// Debouncing signal updates
|
||||
|
||||
// ===== Approach 1: Traditional debouncing with utility function =====
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return (...args)=> {
|
||||
@ -8,8 +9,59 @@ function debounce(func, wait) {
|
||||
};
|
||||
}
|
||||
|
||||
const inputSignal= S("");
|
||||
const debouncedSet= debounce(value => inputSignal.set(value), 300);
|
||||
const inputSignal = S("");
|
||||
const debouncedSet = debounce(value => inputSignal.set(value), 300);
|
||||
|
||||
// In your input handler
|
||||
inputElement.addEventListener("input", e=> debouncedSet(e.target.value));
|
||||
inputElement.addEventListener("input", e => debouncedSet(e.target.value));
|
||||
|
||||
// ===== Approach 2: Signal debouncing utility =====
|
||||
/**
|
||||
* Creates a debounced signal that only updates after delay
|
||||
* @param {any} initialValue Initial signal value
|
||||
* @param {number} delay Debounce delay in ms
|
||||
*/
|
||||
function createDebouncedSignal(initialValue, delay = 300) {
|
||||
// Create two signals: one for immediate updates, one for debounced values
|
||||
const immediateSignal = S(initialValue);
|
||||
const debouncedSignal = S(initialValue);
|
||||
|
||||
// Keep track of the timeout
|
||||
let timeout = null;
|
||||
|
||||
// Set up a listener on the immediate signal
|
||||
S.on(immediateSignal, value => {
|
||||
// Clear any existing timeout
|
||||
if (timeout) clearTimeout(timeout);
|
||||
|
||||
// Set a new timeout to update the debounced signal
|
||||
timeout = setTimeout(() => {
|
||||
debouncedSignal.set(value);
|
||||
}, delay);
|
||||
});
|
||||
|
||||
// Return an object with both signals and a setter function
|
||||
return {
|
||||
// The raw signal that updates immediately
|
||||
raw: immediateSignal,
|
||||
// The debounced signal that only updates after delay
|
||||
debounced: debouncedSignal,
|
||||
// Setter function to update the immediate signal
|
||||
set: value => immediateSignal.set(value)
|
||||
};
|
||||
}
|
||||
|
||||
// Usage example
|
||||
const searchInput = createDebouncedSignal("", 300);
|
||||
|
||||
// Log immediate changes for demonstration
|
||||
S.on(searchInput.raw, value => console.log("Input changed to:", value));
|
||||
|
||||
// Only perform expensive operations on the debounced value
|
||||
S.on(searchInput.debounced, value => {
|
||||
console.log("Performing search with:", value);
|
||||
// Expensive operation would go here
|
||||
});
|
||||
|
||||
// In your input handler
|
||||
searchElement.addEventListener("input", e => searchInput.set(e.target.value));
|
@ -7,8 +7,6 @@ const paragraph= el("p", "See lifecycle events in console.",
|
||||
|
||||
document.body.append(
|
||||
paragraph,
|
||||
el("button", "Update attribute", on("click", ()=> paragraph.setAttribute("test", Math.random().toString()))),
|
||||
" ",
|
||||
el("button", "Remove", on("click", ()=> paragraph.remove()))
|
||||
);
|
||||
|
||||
|
@ -7,7 +7,7 @@ const todos= S([], {
|
||||
const removed= this.value.pop();
|
||||
if(removed) S.clear(removed);
|
||||
},
|
||||
[S.symbols.onclear](){ // this covers `O.clear(todos)`
|
||||
[S.symbols.onclear](){ // this covers `S.clear(todos)`
|
||||
S.clear(...this.value);
|
||||
}
|
||||
});
|
||||
|
@ -14,10 +14,6 @@ export function mnemonic(){
|
||||
el("code", "S.on(<signal>, <listener>[, <options>])"),
|
||||
" — listen to the signal value changes",
|
||||
),
|
||||
el("li").append(
|
||||
el("code", "S.clear(...<signals>)"),
|
||||
" — off and clear signals",
|
||||
),
|
||||
el("li").append(
|
||||
el("code", "S(<value>, <actions>)"),
|
||||
" — signal: pattern to create complex reactive objects/arrays",
|
||||
@ -29,6 +25,11 @@ export function mnemonic(){
|
||||
el("li").append(
|
||||
el("code", "S.el(<signal>, <function-returning-dom>)"),
|
||||
" — render partial dom structure (template) based on the current signal value",
|
||||
)
|
||||
),
|
||||
el("li").append(
|
||||
el("code", "S.clear(...<signals>)"),
|
||||
" — off and clear signals (most of the time it is not needed as reactive ",
|
||||
"attributes and elements are cleared automatically)",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -193,6 +193,20 @@ pre code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
figure {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: var(--text-light);
|
||||
border: 1px dashed var(--border);
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
img {
|
||||
object-fit: contain;
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--shadow);
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
body {
|
||||
|
@ -33,7 +33,7 @@ export function page({ pkg, info }){
|
||||
without the complexity and overhead of larger frameworks.
|
||||
`),
|
||||
el("div", { className: "callout" }).append(
|
||||
el("h4", t`What Makes dd<el> Special`),
|
||||
el("h4", t`Key Benefits of dd<el>`),
|
||||
el("ul").append(
|
||||
el("li", t`No build step required — use directly in the browser`),
|
||||
el("li", t`Lightweight core (~10–15kB minified) without unnecessary dependencies (0 at now 😇)`),
|
||||
@ -44,7 +44,7 @@ export function page({ pkg, info }){
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/introducing/helloWorld.js"), page_id }),
|
||||
|
||||
el(h3, { textContent: t`The 3PS Pattern: A Better Way to Build UIs`, id: "h-3ps" }),
|
||||
el(h3, { textContent: t`The 3PS Pattern: Simplified architecture pattern`, id: "h-3ps" }),
|
||||
el("p").append(T`
|
||||
At the heart of dd<el> is the 3PS (3-Part Separation) pattern. This simple yet powerful approach helps you
|
||||
organize your UI code into three distinct areas, making your applications more maintainable and easier
|
||||
@ -70,7 +70,8 @@ export function page({ pkg, info }){
|
||||
${el("strong", "Create State")}: Define your application’s reactive data using signals
|
||||
`),
|
||||
el("li").append(T`
|
||||
${el("strong", "Bind to Elements")}: Define how UI elements react to state changes
|
||||
${el("strong", "React to Changes")}: Define how UI elements and other parts of your app react to state
|
||||
changes
|
||||
`),
|
||||
el("li").append(T`
|
||||
${el("strong", "Update State")}: Modify state in response to user events or other triggers
|
||||
@ -79,9 +80,9 @@ export function page({ pkg, info }){
|
||||
|
||||
el("p").append(T`
|
||||
By separating these concerns, your code becomes more modular, testable, and easier to maintain. This
|
||||
approach shares principles with more formal patterns like ${el("a", { textContent: "MVVM",
|
||||
...references.w_mvv })} and ${el("a", { textContent: "MVC", ...references.w_mvc })}, but with less
|
||||
overhead and complexity.
|
||||
approach ${el("strong", "is not")} something new and/or special to dd<el>. It’s based on ${el("a", {
|
||||
textContent: "MVC", ...references.w_mvc })} (${el("a", { textContent: "MVVM", ...references.w_mvv })}),
|
||||
but is there presented in simpler form.
|
||||
`),
|
||||
|
||||
el("div", { className: "note" }).append(
|
||||
@ -89,6 +90,10 @@ export function page({ pkg, info }){
|
||||
The 3PS pattern becomes especially powerful when combined with components, allowing you to create
|
||||
reusable pieces of UI with encapsulated state and behavior. You’ll learn more about this in the
|
||||
following sections.
|
||||
`),
|
||||
el("p").append(T`
|
||||
The 3PS pattern isn’t required to use with dd<el> but it is good practice to follow it or some similar
|
||||
software architecture.
|
||||
`)
|
||||
),
|
||||
|
||||
|
@ -104,14 +104,14 @@ export function page({ pkg, info }){
|
||||
el("dd", t`Prefers IDL properties, falls back to setAttribute() when no writable property exists`),
|
||||
|
||||
el("dt", t`Data and ARIA Attributes`),
|
||||
el("dd").append(T`Both ${el("code", "dataset")}.* and ${el("code", "data-")}* syntaxes supported
|
||||
(same for ${el("em", "ARIA")})`),
|
||||
el("dd").append(T`Both ${el("code", "dataset.keyName")} and ${el("code", "dataKeyName")} syntaxes are
|
||||
supported (same for ${el("code", "aria")}/${el("code", "ariaset")})`),
|
||||
|
||||
el("dt", t`Style Handling`),
|
||||
el("dd").append(T`Accepts string or object notation for ${el("code", "style")} property`),
|
||||
|
||||
el("dt", t`Class Management`),
|
||||
el("dd").append(T`Works with ${el("code", "className")}, ${el("code", "class")}, or ${el("code",
|
||||
el("dd").append(T`Works with ${el("code", "className")} (${el("code", "class")}) and ${el("code",
|
||||
"classList")} object for toggling classes`),
|
||||
|
||||
el("dt", t`Force Modes`),
|
||||
|
@ -87,14 +87,14 @@ export function page({ pkg, info }){
|
||||
el(h3, t`Removing Event Listeners`),
|
||||
el("div", { className: "note" }).append(
|
||||
el("p").append(T`
|
||||
Unlike the native addEventListener/removeEventListener pattern, dd<el> uses the ${el("a", {
|
||||
textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative approach for removal:
|
||||
Unlike the native addEventListener/removeEventListener pattern, dd<el> uses ${el("strong", "only")}
|
||||
${el("a", { textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative removal:
|
||||
`)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/events/abortSignal.js"), page_id }),
|
||||
el("p").append(T`
|
||||
This is the same for signals (see next section) and works well with scopes and library extendability (
|
||||
see scopes and extensions section).
|
||||
see scopes and extensions section — mainly ${el("code", "scope.signal")}).
|
||||
`),
|
||||
|
||||
el(h3, t`Three Ways to Handle Events`),
|
||||
@ -152,7 +152,7 @@ export function page({ pkg, info }){
|
||||
el(h3, t`Lifecycle Events`),
|
||||
el("p").append(T`
|
||||
Addons are called immediately when an element is created, even before it’s connected to the live DOM.
|
||||
You can think of an Addon as an "oncreate" event handler.
|
||||
You can think of an Addon as an “oncreate” event handler.
|
||||
`),
|
||||
el("p").append(T`
|
||||
dd<el> provides two additional lifecycle events that correspond to ${el("a", { textContent:
|
||||
|
@ -286,12 +286,13 @@ export function page({ pkg, info }){
|
||||
el("dt", t`UI not updating when array/object changes`),
|
||||
el("dd", t`Use signal actions instead of direct mutation`),
|
||||
|
||||
el("dt", t`UI not updating`),
|
||||
el("dd").append(T`Ensure you passing the (correct) signal not its value (${el("code", "signal")} vs
|
||||
${el("code", "signal.get()")})`),
|
||||
|
||||
el("dt", t`Infinite update loops`),
|
||||
el("dd", t`Check for circular dependencies between signals`),
|
||||
|
||||
el("dt", t`Memory leaks`),
|
||||
el("dd", t`Use AbortController or scope.host() to clean up subscriptions`),
|
||||
|
||||
el("dt", t`Multiple elements updating unnecessarily`),
|
||||
el("dd", t`Split large signals into smaller, more focused ones`)
|
||||
)
|
||||
|
@ -68,7 +68,7 @@ export function page({ pkg, info }){
|
||||
className: "my-component"
|
||||
}).append(
|
||||
el("h2", "Title"),
|
||||
el("p", "Content")
|
||||
el("p", "Content"),
|
||||
);
|
||||
}
|
||||
` })
|
||||
|
@ -25,19 +25,32 @@ export function page({ pkg, info }){
|
||||
el(h3, t`Debugging signals`),
|
||||
el("p").append(T`
|
||||
Signals are reactive primitives that update the UI when their values change. When debugging signals,
|
||||
you need to track their values, understand their dependencies, and identify why updates are or aren't happening.
|
||||
you need to track their values, understand their dependencies, and identify why updates are or aren’t
|
||||
happening.
|
||||
`),
|
||||
|
||||
el("h4", t`Inspecting signal values`),
|
||||
el("p").append(T`
|
||||
The simplest way to debug a signal is to log its current value by calling the get method:
|
||||
The simplest way to debug a signal is to log its current value by calling the get or valueOf method:
|
||||
`),
|
||||
el(code, { content: `
|
||||
const signal = S(0);
|
||||
console.log('Current value:', signal.get());
|
||||
// without triggering updates
|
||||
console.log('Current value:', signal.valueOf());
|
||||
`, page_id }),
|
||||
el("div", { className: "warning" }).append(
|
||||
el("p").append(T`
|
||||
${el("code", "signal.get")} is OK, but in some situations may lead to unexpected results:
|
||||
`),
|
||||
el(code, { content: `
|
||||
const signal = S(0);
|
||||
const derived = S(()=> {
|
||||
console.log('Current value:', signal.get());
|
||||
// ↑ in rare cases this will register unwanted dependency
|
||||
// but typically this is fine ↓
|
||||
return signal.get() + 1;
|
||||
});
|
||||
` })
|
||||
),
|
||||
el("p").append(T`
|
||||
You can also monitor signal changes by adding a listener:
|
||||
`),
|
||||
@ -58,83 +71,7 @@ export function page({ pkg, info }){
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/debugging/consoleLog.js"), page_id }),
|
||||
|
||||
el(h3, t`Common signal debugging issues`),
|
||||
el("h4", t`Signal updates not triggering UI changes`),
|
||||
el("p", t`If signal updates aren’t reflected in the UI, check:`),
|
||||
el("ul").append(
|
||||
el("li", t`That you’re using signal.set() to update the value, not modifying objects/arrays directly`),
|
||||
el("li", t`For mutable objects, ensure you’re using actions or making proper copies before updating`),
|
||||
el("li", t`That the signal is actually connected to the DOM element (check your S.el or attribute binding code)`)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }),
|
||||
|
||||
el("h4", t`Memory leaks with signal listeners`),
|
||||
el("p").append(T`
|
||||
Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal
|
||||
to cancel listeners.
|
||||
`),
|
||||
|
||||
el("h4", t`Performance issues with frequently updating signals`),
|
||||
el("p", t`If you notice performance issues with signals that update very frequently:`),
|
||||
el("ul").append(
|
||||
el("li", t`Consider debouncing or throttling signal updates`),
|
||||
el("li", t`Make sure derived signals don’t perform expensive calculations unnecessarily`),
|
||||
el("li", t`Keep signal computations focused and minimal`)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/debugging/debouncing.js"), page_id }),
|
||||
|
||||
el(h3, t`Browser DevTools tips for dd<el>`),
|
||||
el("p").append(T`
|
||||
When debugging in the browser, dd<el> provides several helpful DevTools-friendly features:
|
||||
`),
|
||||
|
||||
el("h4", t`Identifying components in the DOM`),
|
||||
el("p").append(T`
|
||||
dd<el> marks components in the DOM with special comment nodes to help you identify component boundaries.
|
||||
Components created with ${el("code", "el(ComponentFunction)")} are marked with comment nodes
|
||||
${el("code", `<!--<dde:mark type="component" name="MyComponent" host="parentElement"/>-->`)} and
|
||||
includes:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", t`type - Identifies the type of marker ("component", "reactive", or "later")`),
|
||||
el("li", t`name - The name of the component function`),
|
||||
el("li", t`host - Indicates whether the host is "this" (for DocumentFragments) or "parentElement"`),
|
||||
),
|
||||
|
||||
el("h4", t`Finding reactive elements in the DOM`),
|
||||
el("p").append(T`
|
||||
When using ${el("code", "S.el()")}, dd<el> creates reactive elements in the DOM
|
||||
that are automatically updated when signal values change. These elements are wrapped in special
|
||||
comment nodes for debugging (to be true they are also used internally, so please do not edit them by hand):
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/debugging/dom-reactive-mark.html"), page_id }),
|
||||
el("p").append(T`
|
||||
This is particularly useful when debugging why a reactive section isn’t updating as expected.
|
||||
You can inspect the elements between the comment nodes to see their current state and the
|
||||
signal connections through \`__dde_reactive\` of the host element.
|
||||
`),
|
||||
|
||||
el("h4", t`DOM inspection properties`),
|
||||
el("p").append(T`
|
||||
Elements created with the dd<el> library have special properties to aid in debugging:
|
||||
`),
|
||||
el("p").append(T`
|
||||
${el("code", "<element>.__dde_reactive")} - An array property on DOM elements that tracks signal-to-element
|
||||
relationships. This allows you to quickly identify which elements are reactive and what signals they’re
|
||||
bound to. Each entry in the array contains:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", t`A pair of signal and listener function: [signal, listener]`),
|
||||
el("li", t`Additional context information about the element or attribute`),
|
||||
el("li", t`Automatically managed by signal.el(), signal.observedAttributes(), and processReactiveAttribute()`)
|
||||
),
|
||||
el("p").append(T`
|
||||
These properties make it easier to understand the reactive structure of your application when inspecting
|
||||
elements.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/debugging-dom.js"), page_id }),
|
||||
|
||||
el("h4", t`Examining signal connections`),
|
||||
el("h4", t`Examining signal via DevTools`),
|
||||
el("p").append(T`
|
||||
${el("code", "<signal>.__dde_signal")} - A Symbol property used to identify and store the internal state of
|
||||
signal objects. It contains the following information:
|
||||
@ -148,22 +85,8 @@ export function page({ pkg, info }){
|
||||
el("li", t`readonly: Boolean flag indicating if the signal is read-only`)
|
||||
),
|
||||
el("p").append(T`
|
||||
…to determine the current value of the signal, call ${el("code", "signal.valueOf()")}.
|
||||
`),
|
||||
el("p").append(T`
|
||||
You can inspect (host) element relationships and bindings with signals in the DevTools console using
|
||||
${el("code", "$0.__dde_reactive")} (for currently selected element). In the console you will see a list of
|
||||
${el("code", `[ [ signal, listener ], element, property ]`)}, where:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", t`signal — the signal triggering the changes`),
|
||||
el("li", t`listener — the listener function (this is an internal function for dd<el>)`),
|
||||
el("li", t`element — the DOM element that is bound to the signal`),
|
||||
el("li", t`property — the attribute or property name which is changing based on the signal`),
|
||||
),
|
||||
el("p").append(T`
|
||||
…the structure of \`__dde_reactive\` utilizes the browser’s behavior of packing the first field,
|
||||
so you can see the element and property that changes in the console right away.
|
||||
…to determine the current value of the signal, call ${el("code", "signal.valueOf()")}. Don’t hesitate to
|
||||
use the debugger to inspect the signal object.
|
||||
`),
|
||||
|
||||
el("h4", t`Debugging with breakpoints`),
|
||||
@ -185,5 +108,110 @@ export function page({ pkg, info }){
|
||||
`)
|
||||
),
|
||||
|
||||
el(h3, t`Common signal debugging issues`),
|
||||
el("h4", t`Signal updates not triggering UI changes`),
|
||||
el("p", t`If signal updates aren’t reflected in the UI, check:`),
|
||||
el("ul").append(
|
||||
el("li", t`That you’re using signal.set() to update the value, not modifying objects/arrays directly`),
|
||||
el("li", t`For mutable objects, ensure you’re using actions or making proper copies before updating`),
|
||||
el("li", t`That the signal is actually connected to the DOM element (check your S.el or attribute binding code)`)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }),
|
||||
|
||||
el("h4", t`Memory leaks with signal listeners`),
|
||||
el("p").append(T`
|
||||
Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal
|
||||
to cancel listeners when they are used ouside the dd<el> knowledge (el, assign, S.el, … auto cleanup
|
||||
unnecessarily signals automatically).
|
||||
`),
|
||||
|
||||
el("h4", t`Performance issues with frequently updating signals`),
|
||||
el("p", t`If you notice performance issues with signals that update very frequently:`),
|
||||
el("ul").append(
|
||||
el("li", t`Consider debouncing or throttling signal updates`),
|
||||
el("li", t`Make sure derived signals don’t perform expensive calculations unnecessarily`),
|
||||
el("li", t`Keep signal computations focused and minimal`)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/debugging/debouncing.js"), page_id }),
|
||||
|
||||
el(h3, t`Browser DevTools tips for components and reactivity`),
|
||||
el("p").append(T`
|
||||
When debugging in the browser, dd<el> provides several helpful DevTools-friendly features:
|
||||
`),
|
||||
|
||||
el("h4", t`Finding reactive elements in the DOM`),
|
||||
el("p").append(T`
|
||||
When using ${el("code", "S.el()")}, dd<el> creates reactive elements in the DOM
|
||||
that are automatically updated when signal values change. These elements are wrapped in special
|
||||
comment nodes for debugging (to be true they are also used internally, so please do not edit them by hand):
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/debugging/dom-reactive-mark.html"), page_id }),
|
||||
el("p").append(T`
|
||||
This is particularly useful when debugging why a reactive section isn’t updating as expected.
|
||||
You can inspect the elements between the comment nodes to see their current state and the
|
||||
signal connections through \`__dde_reactive\` of the host element.
|
||||
`),
|
||||
|
||||
el("h4", t`Identifying components in the DOM`),
|
||||
el("p").append(T`
|
||||
dd<el> marks components in the DOM with special comment nodes to help you identify component boundaries.
|
||||
Components created with ${el("code", "el(MyComponent)")} are marked with comment nodes
|
||||
${el("code", `<!--<dde:mark type="component" name="MyComponent" host="parentElement"/>-->`)} and
|
||||
includes:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", t`type - Identifies the type of marker ("component", "reactive", …)`),
|
||||
el("li", t`name - The name of the component function`),
|
||||
el("li", t`host - Indicates whether the host is "this" (for DocumentFragments) or "parentElement"`),
|
||||
),
|
||||
|
||||
el("h4", t`Identifying reactive elements in the DOM`),
|
||||
el("p").append(T`
|
||||
You can inspect (host) element relationships and bindings with signals in the DevTools console using
|
||||
${el("code", "$0.__dde_reactive")} (for currently selected element). In the console you will see a list of
|
||||
${el("code", `[ [ signal, listener ], element, property ]`)}, where:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", t`signal — the signal triggering the changes`),
|
||||
el("li", t`listener — the listener function (this is an internal function for dd<el>)`),
|
||||
el("li", t`element — the DOM element that is bound to the signal`),
|
||||
el("li", t`property — the attribute or property name which is changing based on the signal`),
|
||||
),
|
||||
el("p").append(T`
|
||||
…the structure of \`__dde_reactive\` utilizes the browser’s behavior of packing the first field,
|
||||
so you can see the element and property that changes in the console right away. These properties make it
|
||||
easier to understand the reactive structure of your application when inspecting elements.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/debugging-dom.js"), page_id }),
|
||||
|
||||
el("p", { className: "note" }).append(T`
|
||||
${el("code", "<element>.__dde_reactive")} - An array property on DOM elements that tracks signal-to-element
|
||||
relationships. This allows you to quickly identify which elements are reactive and what signals they’re
|
||||
bound to. Each entry in the array contains:
|
||||
`),
|
||||
|
||||
el("h4", t`Inspecting events and listeners in DevTools`),
|
||||
el("p").append(T`
|
||||
Modern browser DevTools provide built-in tools for inspecting event listeners attached to DOM elements.
|
||||
For example, in Firefox and Chrome, you can:
|
||||
`),
|
||||
el("ol").append(
|
||||
el("li", t`Select an element in the Elements/Inspector panel`),
|
||||
el("li", t`Look for the "Event Listeners" tab or section`),
|
||||
el("li", t`See all event listeners attached to the element, including those added by dd<el>`)
|
||||
),
|
||||
el("p").append(T`
|
||||
Additionally, dd<el> provides special markers in the DOM that help identify debug information.
|
||||
Look for comments with ${el("code", "dde:mark")}, ${el("code", "dde:disconnected")} and ${el("code",
|
||||
"__dde_reactive")} which indicate components, reactive regions, and other internal relationships:
|
||||
`),
|
||||
el("figure").append(
|
||||
el("img", {
|
||||
src: "./assets/devtools.png",
|
||||
alt: "Screenshot of DevTools showing usage of “event” button to inspect event listeners",
|
||||
}),
|
||||
el("figcaption", t`Firefox DevTools showing dd<el> debugging information with event listeners and reactive
|
||||
markers`)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -91,7 +91,6 @@ export function page({ pkg, info }){
|
||||
This approach enables better interoperability and future-proofing.
|
||||
`),
|
||||
el("div", { className: "illustration" }).append(
|
||||
el("h4", t`Library-Independent vs. Library-Dependent Extension`),
|
||||
el("div", { className: "tabs" }).append(
|
||||
el("div", { className: "tab" }).append(
|
||||
el("h5", t`✅ Library-Independent`),
|
||||
|
@ -189,6 +189,10 @@ export function page({ pkg, info }){
|
||||
signal: controller.signal
|
||||
});
|
||||
`, page_id }),
|
||||
el("p").append(T`
|
||||
You can use custom memo scope as function in (e. g. ${el("code", "S.el(signal, renderList)")}) and as
|
||||
(Abort) signal use ${el("code", "scope.signal")}.
|
||||
`),
|
||||
|
||||
el("div", { className: "function-table" }).append(
|
||||
el("dl").append(
|
||||
@ -210,10 +214,8 @@ export function page({ pkg, info }){
|
||||
Signals are efficient, but unnecessary updates can impact performance:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", t`Avoid setting signal values that haven't actually changed`),
|
||||
el("li", t`For frequently updating values (like scroll position), consider debouncing`),
|
||||
el("li", t`Keep signal computations small and focused`),
|
||||
el("li", t`Use derived signals to compute values only when dependencies change`)
|
||||
),
|
||||
|
||||
el("h4", t`Optimizing List Rendering`),
|
||||
@ -250,32 +252,44 @@ export function page({ pkg, info }){
|
||||
|
||||
el("h4", t`Choosing the Right Optimization Approach`),
|
||||
el("p").append(T`
|
||||
While memo is powerful, it's not always the best solution:
|
||||
While ${el("code", "memo")} is powerful, different scenarios call for different optimization techniques:
|
||||
`),
|
||||
el("table").append(
|
||||
el("thead").append(
|
||||
el("tr").append(
|
||||
el("th", "Approach"),
|
||||
el("th", "When to use")
|
||||
)
|
||||
),
|
||||
el("tbody").append(
|
||||
el("tr").append(
|
||||
el("td", "memo"),
|
||||
el("td", "Lists with stable items that infrequently change position")
|
||||
),
|
||||
el("tr").append(
|
||||
el("td", "Signal computations"),
|
||||
el("td", "Derived values that depend on other signals")
|
||||
),
|
||||
el("tr").append(
|
||||
el("td", "Debouncing"),
|
||||
el("td", "High-frequency events like scroll or resize")
|
||||
),
|
||||
el("tr").append(
|
||||
el("td", "Stateful components"),
|
||||
el("td", "Complex components with internal state")
|
||||
)
|
||||
el("div", { className: "function-table" }).append(
|
||||
el("dl").append(
|
||||
el("dt", t`memo`),
|
||||
el("dd").append(T`
|
||||
Best for list rendering where items rarely change or only their properties update.
|
||||
${el("code", "todos.map(todo => memo(todo.id, () => el(TodoItem, todo)))")}
|
||||
Use when you need to cache and reuse DOM elements to avoid recreating them on every render.
|
||||
`),
|
||||
|
||||
el("dt", t`Signal computations`),
|
||||
el("dd").append(T`
|
||||
Ideal for derived values that depend on other signals and need to auto-update.
|
||||
${el("code", "const totalPrice = S(() => items.get().reduce((t, i) => t + i.price, 0))")}
|
||||
Use when calculated values need to stay in sync with changing source data.
|
||||
`),
|
||||
|
||||
el("dt", t`Debouncing/Throttling`),
|
||||
el("dd").append(T`
|
||||
Essential for high-frequency events (scroll, resize) or rapidly changing input values.
|
||||
${el("code", "debounce(e => searchQuery.set(e.target.value), 300)")}
|
||||
Use to limit the rate at which expensive operations execute when triggered by fast events.
|
||||
`),
|
||||
|
||||
el("dt", t`memo.scope`),
|
||||
el("dd").append(T`
|
||||
Useful for using memoization inside any function: ${el("code",
|
||||
"const renderList = memo.scope(items => items.map(...))")}. Use to create isolated memoization
|
||||
contexts that can be cleared or managed independently.
|
||||
`),
|
||||
|
||||
el("dt", t`Stateful components`),
|
||||
el("dd").append(T`
|
||||
For complex UI components with internal state management.
|
||||
${el("code", "el(ComplexComponent, { initialState, onChange })")}
|
||||
Use when a component needs to encapsulate and manage its own state and lifecycle.
|
||||
`)
|
||||
)
|
||||
),
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user