$for allow no keyFn
This commit is contained in:
5
dist/sigpro.js
vendored
5
dist/sigpro.js
vendored
@@ -316,7 +316,7 @@
|
|||||||
return container;
|
return container;
|
||||||
};
|
};
|
||||||
$if.not = (condition, thenVal, otherwiseVal) => $if(() => !(typeof condition === "function" ? condition() : condition), thenVal, otherwiseVal);
|
$if.not = (condition, thenVal, otherwiseVal) => $if(() => !(typeof condition === "function" ? condition() : condition), thenVal, otherwiseVal);
|
||||||
var $for = (source, render, keyFn) => {
|
var $for = (source, render, keyFn = (item, index) => index) => {
|
||||||
const marker = document.createTextNode("");
|
const marker = document.createTextNode("");
|
||||||
const container = $html("div", { style: "display:contents" }, [marker]);
|
const container = $html("div", { style: "display:contents" }, [marker]);
|
||||||
const cache = new Map;
|
const cache = new Map;
|
||||||
@@ -336,6 +336,9 @@
|
|||||||
cache.forEach((run, key) => {
|
cache.forEach((run, key) => {
|
||||||
if (!newKeys.has(key)) {
|
if (!newKeys.has(key)) {
|
||||||
run.destroy();
|
run.destroy();
|
||||||
|
if (run.container && run.container.parentNode) {
|
||||||
|
run.container.remove();
|
||||||
|
}
|
||||||
cache.delete(key);
|
cache.delete(key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
2
dist/sigpro.min.js
vendored
2
dist/sigpro.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -8,7 +8,7 @@ The `$for` function is a high-performance list renderer. It maps an array (or a
|
|||||||
$for(
|
$for(
|
||||||
source: Signal<any[]> | Function | any[],
|
source: Signal<any[]> | Function | any[],
|
||||||
render: (item: any, index: number) => HTMLElement,
|
render: (item: any, index: number) => HTMLElement,
|
||||||
keyFn: (item: any, index: number) => string | number
|
keyFn?: (item: any, index: number) => string | number
|
||||||
): HTMLElement
|
): HTMLElement
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ $for(
|
|||||||
| :--- | :--- | :--- | :--- |
|
| :--- | :--- | :--- | :--- |
|
||||||
| **`source`** | `Signal` | Yes | The reactive array to iterate over. |
|
| **`source`** | `Signal` | Yes | The reactive array to iterate over. |
|
||||||
| **`render`** | `Function` | Yes | A function that returns a component or Node for each item. |
|
| **`render`** | `Function` | Yes | A function that returns a component or Node for each item. |
|
||||||
| **`keyFn`** | `Function` | Yes | A function to extract a **unique ID** for each item (crucial for performance). |
|
| **`keyFn`** | `Function` | **No** | A function to extract a **unique ID**. If omitted, it defaults to the `index`. |
|
||||||
|
|
||||||
**Returns:** A `div` element with `display: contents` containing the live list.
|
**Returns:** A `div` element with `display: contents` containing the live list.
|
||||||
|
|
||||||
@@ -24,8 +24,8 @@ $for(
|
|||||||
|
|
||||||
## Usage Patterns
|
## Usage Patterns
|
||||||
|
|
||||||
### 1. Basic Keyed List
|
### 1. Basic Keyed List (Recommended)
|
||||||
Always use a unique property (like an `id`) as a key to ensure SigPro doesn't recreate nodes unnecessarily.
|
Always use a unique property (like an `id`) as a key to ensure SigPro doesn't recreate nodes unnecessarily when reordering or filtering.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const users = $([
|
const users = $([
|
||||||
@@ -36,19 +36,20 @@ const users = $([
|
|||||||
Ul({ class: "list" }, [
|
Ul({ class: "list" }, [
|
||||||
$for(users,
|
$for(users,
|
||||||
(user) => Li({ class: "p-2" }, user.name),
|
(user) => Li({ class: "p-2" }, user.name),
|
||||||
(user) => user.id
|
(user) => user.id // Stable and unique key
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Handling Primitive Arrays
|
### 2. Simplified Usage (Automatic Key)
|
||||||
If your array contains simple strings or numbers, you can use the value itself or the index as a key (though the index is less efficient for reordering).
|
If you omit the third parameter, `$for` will automatically use the array index as the key. This is ideal for simple lists that don't change order frequently.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const tags = $(["Tech", "JS", "Web"]);
|
const tags = $(["Tech", "JS", "Web"]);
|
||||||
|
|
||||||
|
// No need to pass keyFn if the index is sufficient
|
||||||
Div({ class: "flex gap-1" }, [
|
Div({ class: "flex gap-1" }, [
|
||||||
$for(tags, (tag) => Badge(tag), (tag) => tag)
|
$for(tags, (tag) => Badge(tag))
|
||||||
]);
|
]);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -60,16 +61,14 @@ When the `source` signal changes, `$for` performs the following steps:
|
|||||||
|
|
||||||
1. **Key Diffing**: It compares the new keys with the previous ones stored in an internal `Map`.
|
1. **Key Diffing**: It compares the new keys with the previous ones stored in an internal `Map`.
|
||||||
2. **Node Reuse**: If a key already exists, the DOM node is **reused** and moved to its new position. No new elements are created.
|
2. **Node Reuse**: If a key already exists, the DOM node is **reused** and moved to its new position. No new elements are created.
|
||||||
3. **Cleanup**: If a key disappears from the list, SigPro calls `.destroy()` on that specific item's instance. This stops all its internal watchers and removes its DOM nodes.
|
3. **Physical Cleanup**: If a key disappears from the list, SigPro calls `.destroy()` to stop reactivity and physically removes the node from the DOM to prevent memory leaks.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Performance Tips
|
## Performance Tips
|
||||||
|
|
||||||
* **Stable Keys**: Never use `Math.random()` as a key. This will force SigPro to destroy and recreate the entire list on every update, killing performance.
|
* **Stable Keys**: Never use `Math.random()` as a key. This will force SigPro to destroy and recreate the entire list on every update, killing performance.
|
||||||
* **Component Encapsulation**: If each item in your list has its own complex internal state, `$for` ensures that state is preserved even if the list is reordered, as long as the key remains the same.
|
* **State Preservation**: If your list items have internal state (like an input with text), `$for` ensures that state is preserved even if the list is reordered, as long as the key (`id`) remains the same.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -81,3 +80,4 @@ When the `source` signal changes, `$for` performs the following steps:
|
|||||||
| **DOM Nodes** | Re-created every time | **Reused via Keys** |
|
| **DOM Nodes** | Re-created every time | **Reused via Keys** |
|
||||||
| **Memory** | Potential leaks | **Automatic Cleanup** |
|
| **Memory** | Potential leaks | **Automatic Cleanup** |
|
||||||
| **State** | Lost on re-render | **Preserved per item** |
|
| **State** | Lost on re-render | **Preserved per item** |
|
||||||
|
| **Ease of Use** | Manual logic required | **Optional (fallback to index)** |
|
||||||
@@ -307,27 +307,39 @@ $if.not = (condition, thenVal, otherwiseVal) => $if(() => !(typeof condition ===
|
|||||||
* @returns {HTMLElement} A reactive container (display: contents).
|
* @returns {HTMLElement} A reactive container (display: contents).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const $for = (source, render, keyFn) => {
|
const $for = (source, render, keyFn = (item, index) => index) => {
|
||||||
const marker = document.createTextNode("");
|
const marker = document.createTextNode("");
|
||||||
const container = $html("div", { style: "display:contents" }, [marker]);
|
const container = $html("div", { style: "display:contents" }, [marker]);
|
||||||
const cache = new Map();
|
const cache = new Map();
|
||||||
|
|
||||||
$watch(() => {
|
$watch(() => {
|
||||||
const items = (typeof source === "function" ? source() : source) || [];
|
const items = (typeof source === "function" ? source() : source) || [];
|
||||||
const newKeys = new Set();
|
const newKeys = new Set();
|
||||||
|
|
||||||
items.forEach((item, index) => {
|
items.forEach((item, index) => {
|
||||||
const key = keyFn(item, index);
|
const key = keyFn(item, index);
|
||||||
newKeys.add(key);
|
newKeys.add(key);
|
||||||
|
|
||||||
let run = cache.get(key);
|
let run = cache.get(key);
|
||||||
if (!run) {
|
if (!run) {
|
||||||
run = _view(() => render(item, index));
|
run = _view(() => render(item, index));
|
||||||
cache.set(key, run);
|
cache.set(key, run);
|
||||||
}
|
}
|
||||||
|
|
||||||
container.insertBefore(run.container, marker);
|
container.insertBefore(run.container, marker);
|
||||||
});
|
});
|
||||||
|
|
||||||
cache.forEach((run, key) => {
|
cache.forEach((run, key) => {
|
||||||
if (!newKeys.has(key)) { run.destroy(); cache.delete(key); }
|
if (!newKeys.has(key)) {
|
||||||
|
run.destroy();
|
||||||
|
if (run.container && run.container.parentNode) {
|
||||||
|
run.container.remove();
|
||||||
|
}
|
||||||
|
cache.delete(key);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user