weifuwu 0.36.1 → 0.36.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -345,21 +345,28 @@ function Counter(_props: {}, ctx: WfuiContext) {
345
345
 
346
346
  ### 生命周期 —— ref 回调
347
347
 
348
+ `ref` 回调在 mount 时触发,接收 DOM 元素。返回的清理函数在 unmount 时由框架保证调用。
349
+
348
350
  ```tsx
349
351
  function MyComponent(_props: {}, ctx: WfuiContext) {
350
- function onRef(el: HTMLElement | null) {
351
- if (el) {
352
- // mount: 加事件监听等
352
+ return (
353
+ <div ref={el => {
354
+ // mount: el 是 DOM 元素
353
355
  el.addEventListener('scroll', handler)
354
- } else {
355
- // unmount: 清理
356
- }
357
- }
358
-
359
- return <div ref={onRef} />
356
+ // 返回清理函数,unmount 时框架保证调用
357
+ return () => el.removeEventListener('scroll', handler)
358
+ }} />
359
+ )
360
360
  }
361
361
  ```
362
362
 
363
+ | 场景 | 写法 |
364
+ |------|------|
365
+ | 事件监听 | `ref={el => { el.addEventListener(...); return () => el.removeEventListener(...) }}` |
366
+ | 定时器 | `ref={el => { const t = setInterval(f, 1000); return () => clearInterval(t) }}` |
367
+ | 第三方库 | `ref={el => { const c = new Chart(el); return () => c.destroy() }}` |
368
+ | 仅 mount | `ref={el => { init(el) }}` |
369
+
363
370
  ### 应用 —— createApp
364
371
 
365
372
  ```tsx
@@ -56,7 +56,8 @@ function renderValue(v, ctx) {
56
56
  el.appendChild(renderValue(child, ctx));
57
57
  }
58
58
  if (vnode.props?.ref) {
59
- queueMicrotask(() => vnode.props.ref(el));
59
+ const result = vnode.props.ref(el);
60
+ if (typeof result === "function") vnode._cleanup = result;
60
61
  }
61
62
  return el;
62
63
  }
@@ -140,7 +141,7 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
140
141
  }
141
142
  if (newInput == null) {
142
143
  if (oldNode) {
143
- callRef(oldInput, null);
144
+ callRefCleanup(oldInput);
144
145
  oldNode.remove();
145
146
  }
146
147
  return null;
@@ -148,7 +149,7 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
148
149
  const oldType = typeOf(oldInput);
149
150
  const newType = typeOf(newInput);
150
151
  if (oldType !== newType) {
151
- callRef(oldInput, null);
152
+ callRefCleanup(oldInput);
152
153
  const node = renderValue(newInput, ctx);
153
154
  if (oldNode?.parentNode) {
154
155
  oldNode.parentNode.replaceChild(node, oldNode);
@@ -181,7 +182,7 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
181
182
  patchProps(oldNode, oldV.props, newV.props);
182
183
  patchChildren(oldNode, oldV, newV, ctx);
183
184
  } else if (oldNode) {
184
- callRef(oldInput, null);
185
+ callRefCleanup(oldInput);
185
186
  const node = renderValue(newInput, ctx);
186
187
  oldNode.parentNode?.replaceChild(node, oldNode);
187
188
  return node;
@@ -286,7 +287,7 @@ function patchSimpleChildren(parent, oldChildren, newChildren, ctx) {
286
287
  parent.appendChild(node);
287
288
  } else if (newChild === void 0) {
288
289
  if (existingNode) {
289
- callRef(oldChild, null);
290
+ callRefCleanup(oldChild);
290
291
  existingNode.remove();
291
292
  }
292
293
  } else {
@@ -306,7 +307,7 @@ function patchKeyedChildren(parent, oldChildren, newChildren, ctx) {
306
307
  for (const key of oldKeyMap.keys()) {
307
308
  if (!newKeys.includes(key)) {
308
309
  const entry = oldKeyMap.get(key);
309
- callRef(entry.vnode, null);
310
+ callRefCleanup(entry.vnode);
310
311
  entry.node?.remove();
311
312
  oldKeyMap.delete(key);
312
313
  }
@@ -316,7 +317,7 @@ function patchKeyedChildren(parent, oldChildren, newChildren, ctx) {
316
317
  if (key === void 0) {
317
318
  const node = parent.childNodes[i];
318
319
  if (node) {
319
- callRef(oldChildren[i], null);
320
+ callRefCleanup(oldChildren[i]);
320
321
  node.remove();
321
322
  }
322
323
  }
@@ -337,13 +338,21 @@ function patchKeyedChildren(parent, oldChildren, newChildren, ctx) {
337
338
  }
338
339
  }
339
340
  }
340
- function callRef(input, el) {
341
+ function runRefCleanup(vnode) {
342
+ if (vnode._cleanup) {
343
+ vnode._cleanup();
344
+ vnode._cleanup = void 0;
345
+ }
346
+ forEach(vnode.props?.children, (child) => {
347
+ if (child && typeof child === "object" && child._cleanup) {
348
+ runRefCleanup(child);
349
+ }
350
+ });
351
+ }
352
+ function callRefCleanup(input) {
341
353
  if (input == null || typeof input !== "object") return;
342
354
  const vnode = input;
343
- if (typeof vnode.props?.ref === "function") {
344
- vnode.props.ref(el);
345
- }
346
- forEach(vnode.props?.children, (child) => callRef(child, el));
355
+ if (vnode._cleanup) runRefCleanup(vnode);
347
356
  }
348
357
 
349
358
  // src/client/router.ts
@@ -16,6 +16,8 @@ export interface VNode {
16
16
  _$?: Record<string, any>;
17
17
  /** 子 VNode 缓存(用于 patchValue diff,避免重复执行组件) */
18
18
  _child?: any;
19
+ /** ref 回调返回的清理函数,卸载时由框架调用 */
20
+ _cleanup?: (() => void) | undefined;
19
21
  }
20
22
  export type Component<P = {}> = (props: P, ctx: WfuiContext) => VNode | null;
21
23
  export declare const Fragment: unique symbol;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weifuwu",
3
3
  "type": "module",
4
- "version": "0.36.1",
4
+ "version": "0.36.2",
5
5
  "description": "AI SaaS framework — (req, ctx) => Response",
6
6
  "exports": {
7
7
  ".": {