0
# Advanced Features
1
2
Advanced Snabbdom features including performance optimization through thunks, comprehensive lifecycle hooks, and helper utilities for complex use cases.
3
4
## Capabilities
5
6
### Thunks
7
8
Thunks provide performance optimization by deferring virtual node creation until necessary, ideal for expensive computations with immutable data.
9
10
```typescript { .api }
11
/**
12
* Create a thunk for performance optimization
13
* @param sel - Element selector
14
* @param fn - Function that returns a virtual node
15
* @param args - Arguments passed to the function
16
* @returns Thunked virtual node
17
*/
18
function thunk(
19
sel: string,
20
fn: (...args: any[]) => any,
21
args: any[]
22
): VNode;
23
24
/**
25
* Create a thunk with a key for proper identification
26
* @param sel - Element selector
27
* @param key - Unique key for the thunk
28
* @param fn - Function that returns a virtual node
29
* @param args - Arguments passed to the function
30
* @returns Thunked virtual node
31
*/
32
function thunk(
33
sel: string,
34
key: any,
35
fn: (...args: any[]) => any,
36
args: any[]
37
): VNode;
38
```
39
40
**Usage Examples:**
41
42
```typescript
43
import { thunk, h, init } from "snabbdom";
44
45
// Expensive computation function
46
function createComplexView(data: any[]): VNode {
47
// This function is only called when data changes
48
return h("div.complex", data.map((item, i) =>
49
h("div.item", { key: i }, `Item: ${item.name}`)
50
));
51
}
52
53
// Using thunk to optimize
54
function render(state: { items: any[] }): VNode {
55
return h("div.app", [
56
h("h1", "My App"),
57
// Only re-renders when state.items changes
58
thunk("div.items", createComplexView, [state.items])
59
]);
60
}
61
62
// With key for sibling thunks
63
function renderWithKey(state: { users: any[], posts: any[] }): VNode {
64
return h("div.app", [
65
thunk("div.users", "users", createUsersList, [state.users]),
66
thunk("div.posts", "posts", createPostsList, [state.posts])
67
]);
68
}
69
```
70
71
### Lifecycle Hooks
72
73
Comprehensive hook system providing fine-grained control over the virtual DOM lifecycle.
74
75
```typescript { .api }
76
interface Hooks {
77
pre?: PreHook;
78
init?: InitHook;
79
create?: CreateHook;
80
insert?: InsertHook;
81
prepatch?: PrePatchHook;
82
update?: UpdateHook;
83
postpatch?: PostPatchHook;
84
destroy?: DestroyHook;
85
remove?: RemoveHook;
86
post?: PostHook;
87
}
88
89
type PreHook = () => any;
90
type InitHook = (vNode: VNode) => any;
91
type CreateHook = (emptyVNode: VNode, vNode: VNode) => any;
92
type InsertHook = (vNode: VNode) => any;
93
type PrePatchHook = (oldVNode: VNode, vNode: VNode) => any;
94
type UpdateHook = (oldVNode: VNode, vNode: VNode) => any;
95
type PostPatchHook = (oldVNode: VNode, vNode: VNode) => any;
96
type DestroyHook = (vNode: VNode) => any;
97
type RemoveHook = (vNode: VNode, removeCallback: () => void) => any;
98
type PostHook = () => any;
99
```
100
101
**Hook Usage Examples:**
102
103
```typescript
104
import { h, init, VNode } from "snabbdom";
105
106
// Element-level hooks
107
const elementWithHooks = h("div.animated", {
108
hook: {
109
init: (vnode: VNode) => {
110
console.log("Element initialized");
111
},
112
create: (emptyVnode: VNode, vnode: VNode) => {
113
console.log("DOM element created");
114
},
115
insert: (vnode: VNode) => {
116
// Safe to measure DOM here
117
const rect = (vnode.elm as Element).getBoundingClientRect();
118
console.log("Element inserted, size:", rect);
119
},
120
update: (oldVnode: VNode, vnode: VNode) => {
121
console.log("Element updated");
122
},
123
remove: (vnode: VNode, removeCallback: () => void) => {
124
// Animate out before removing
125
const elm = vnode.elm as HTMLElement;
126
elm.style.opacity = "0";
127
setTimeout(removeCallback, 300);
128
},
129
destroy: (vnode: VNode) => {
130
console.log("Element destroyed");
131
}
132
}
133
});
134
135
// Module-level hooks example
136
const animationModule = {
137
create: (emptyVnode: VNode, vnode: VNode) => {
138
const elm = vnode.elm as HTMLElement;
139
if (elm && vnode.data?.animate) {
140
elm.style.opacity = "0";
141
}
142
},
143
insert: (vnode: VNode) => {
144
const elm = vnode.elm as HTMLElement;
145
if (elm && vnode.data?.animate) {
146
setTimeout(() => {
147
elm.style.transition = "opacity 0.3s";
148
elm.style.opacity = "1";
149
}, 10);
150
}
151
}
152
};
153
154
const patch = init([animationModule]);
155
```
156
157
### AttachTo Helper
158
159
Utility for attaching virtual nodes to different DOM locations than their logical parent.
160
161
```typescript { .api }
162
/**
163
* Attach a virtual node to a different DOM location
164
* @param target - DOM element to attach to
165
* @param vnode - Virtual node to attach
166
* @returns Modified virtual node with attachment behavior
167
*/
168
function attachTo(target: Element, vnode: VNode): VNode;
169
170
interface AttachData {
171
[key: string]: any;
172
[i: number]: any;
173
placeholder?: any;
174
real?: Node;
175
}
176
```
177
178
**Usage Example:**
179
180
```typescript
181
import { attachTo, h, init } from "snabbdom";
182
183
const patch = init([]);
184
185
// Modal that renders in document.body but is managed in component tree
186
function Modal(props: { isOpen: boolean }): VNode {
187
if (!props.isOpen) {
188
return h("div"); // Empty placeholder
189
}
190
191
const modalContent = h("div.modal", [
192
h("div.modal-backdrop"),
193
h("div.modal-content", [
194
h("h2", "Modal Title"),
195
h("p", "Modal content goes here"),
196
h("button", "Close")
197
])
198
]);
199
200
// Attach modal to document.body instead of normal DOM position
201
return attachTo(document.body, modalContent);
202
}
203
204
// Usage in app
205
const app = h("div.app", [
206
h("h1", "Main App"),
207
h("button", "Open Modal"),
208
Modal({ isOpen: true }) // Renders in document.body
209
]);
210
```
211
212
### Utility Functions
213
214
Helper functions for type checking and manipulation.
215
216
```typescript { .api }
217
/**
218
* Check if value is an array (alias for Array.isArray)
219
*/
220
const array: (arg: any) => arg is any[];
221
222
/**
223
* Check if value is a primitive type (string or number)
224
*/
225
function primitive(s: any): s is string | number;
226
227
/**
228
* Default HTML DOM API implementation
229
*/
230
const htmlDomApi: DOMAPI;
231
```
232
233
**Usage Example:**
234
235
```typescript
236
import { array, primitive, htmlDomApi } from "snabbdom";
237
238
// Type checking utilities
239
const data = [1, 2, 3];
240
if (array(data)) {
241
console.log("Is array:", data.length);
242
}
243
244
const value = "hello";
245
if (primitive(value)) {
246
console.log("Is primitive:", value.toString());
247
}
248
249
// Custom DOM API (for testing or server environments)
250
const customDomApi = {
251
...htmlDomApi,
252
createElement: (tagName: string) => {
253
console.log("Creating element:", tagName);
254
return htmlDomApi.createElement(tagName);
255
}
256
};
257
258
const patch = init([], customDomApi);
259
```
260
261
## Advanced Patterns
262
263
### Component State Management
264
265
Using hooks to manage component-level state and effects.
266
267
```typescript
268
import { h, VNode, init } from "snabbdom";
269
270
interface ComponentState {
271
count: number;
272
mounted: boolean;
273
}
274
275
function Counter(initialCount: number = 0): VNode {
276
let state: ComponentState = { count: initialCount, mounted: false };
277
278
const increment = () => {
279
state.count++;
280
// Trigger re-render (you'd implement this in your app)
281
// render();
282
};
283
284
return h("div.counter", {
285
hook: {
286
insert: (vnode: VNode) => {
287
state.mounted = true;
288
console.log("Counter mounted with count:", state.count);
289
},
290
destroy: (vnode: VNode) => {
291
state.mounted = false;
292
console.log("Counter unmounted");
293
}
294
}
295
}, [
296
h("p", `Count: ${state.count}`),
297
h("button", {
298
on: { click: increment }
299
}, "Increment")
300
]);
301
}
302
```
303
304
### Performance Monitoring
305
306
Using hooks to monitor rendering performance.
307
308
```typescript
309
const performanceModule = {
310
pre: () => {
311
console.time("patch");
312
},
313
post: () => {
314
console.timeEnd("patch");
315
}
316
};
317
318
const patch = init([performanceModule]);
319
```
320
321
## Types
322
323
```typescript { .api }
324
interface ThunkData extends VNodeData {
325
fn: () => VNode;
326
args: any[];
327
}
328
329
interface Thunk extends VNode {
330
data: ThunkData;
331
}
332
333
interface ThunkFn {
334
(sel: string, fn: (...args: any[]) => any, args: any[]): Thunk;
335
(sel: string, key: any, fn: (...args: any[]) => any, args: any[]): Thunk;
336
}
337
```