@viewfly/core 3.0.0 → 3.0.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.en.md ADDED
@@ -0,0 +1,71 @@
1
+ # @viewfly/core
2
+
3
+ **Languages:** [简体中文](./README.md)
4
+
5
+ The **core** Viewfly package: function components, JSX, reactivity and `signal`, `watch`, lifecycle, `inject` / IoC APIs, and **`withMark`** for UI-facing utilities.
6
+
7
+ To mount in the browser use **`createApp`** from **`@viewfly/platform-browser`** (built on this package’s application model).
8
+
9
+ ---
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install @viewfly/core
15
+ ```
16
+
17
+ This package depends on **`reflect-metadata`**. Importing from the **`@viewfly/core`** main entry initializes it; if DI breaks after aggressive splitting, add at the **top** of your entry:
18
+
19
+ ```ts
20
+ import 'reflect-metadata'
21
+ ```
22
+
23
+ Prefer the **`.d.ts`** shipped with your installed version and the official docs (<https://viewfly.org>) as the source of truth.
24
+
25
+ ---
26
+
27
+ ## JSX / TSX setup
28
+
29
+ Enable automatic JSX runtime and point it at this package:
30
+
31
+ ```json
32
+ {
33
+ "compilerOptions": {
34
+ "jsx": "react-jsx",
35
+ "jsxImportSource": "@viewfly/core"
36
+ }
37
+ }
38
+ ```
39
+
40
+ With Babel, align `@babel/preset-react` (`runtime: "automatic"`, `importSource: "@viewfly/core"`).
41
+
42
+ ---
43
+
44
+ ## Capabilities (user-facing)
45
+
46
+ Common directions below — **exact types and parameters come from `.d.ts` and the docs**.
47
+
48
+ | Area | Public API hints |
49
+ |------|------------------|
50
+ | Components | Function components as JSX tags; pair with lifecycle hooks. |
51
+ | Lifecycle | **`onMounted`**, **`onUpdated`**, **`onUnmounted`**, etc. (call during component setup). |
52
+ | Reactivity | **`reactive`**, **`shallowReactive`**, **`watch`**, … (`reactive` module). |
53
+ | `signal` / derived | **`createSignal`**, **`computed`**, … (see `reactive` exports). |
54
+ | DI | **`inject`** in components; **`Injectable()`** on classes; **`withAnnotation`** or **`createContext`** / **`createContextProvider`** for providers; root **`createApp(...).provide(...)`** from **`@viewfly/platform-browser`**. |
55
+ | DOM marks | **`withMark(marks, setup)`** — attach attributes (e.g. scoped CSS `scopeId`). |
56
+
57
+ Symbols marked **`@internal`** or undocumented on the site are for framework/experimental use — **avoid relying on them** in apps; they may change without semver guarantees.
58
+
59
+ ---
60
+
61
+ ## Docs & examples
62
+
63
+ - **Official docs:** <https://viewfly.org> (install, components, reactivity, DI).
64
+ - **Types & comments:** published **`.d.ts`** and source comments.
65
+ - **This repo:** `pnpm install` at root, then `pnpm dev` for the playground.
66
+
67
+ ---
68
+
69
+ ## License
70
+
71
+ MIT
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @viewfly/core
2
2
 
3
+ **Languages:** [English](./README.en.md)
4
+
3
5
  Viewfly 的**内核包**:函数组件、`JSX`、响应式与 `signal`、`watch`、生命周期、`inject` 与 IoC 相关 API、以及 **`withMark`** 等与 UI 逻辑直接相关的入口均由此包提供。
4
6
 
5
7
  在浏览器里挂载应用请使用 **`@viewfly/platform-browser`** 的 **`createApp`**(其内部使用本包提供的应用模型)。
@@ -28,7 +28,7 @@ export declare class Component {
28
28
  props: Record<string, any>;
29
29
  readonly key?: Key;
30
30
  instance: ComponentInstance;
31
- changedSubComponents: Set<Component>;
31
+ changedSubComponents: Set<Component> | null;
32
32
  get dirty(): boolean;
33
33
  get changed(): boolean;
34
34
  /**
@@ -48,13 +48,13 @@ export declare class Component {
48
48
  constructor(parentComponent: Component | null, type: ComponentSetup, props: Record<string, any>, key?: Key);
49
49
  markAsDirtied(): void;
50
50
  markAsChanged(changedComponent?: Component): void;
51
- render(update: (template: JSXNode) => void): void;
51
+ render(): JSXNode;
52
52
  updateProps(newProps: Record<string, any>): void;
53
- rerender(): JSXNode;
54
53
  destroy(): void;
55
54
  rendered(): void;
56
55
  private invokeMountHooks;
57
56
  private invokeUpdatedHooks;
57
+ private getOrCreateRefEffects;
58
58
  }
59
59
  /**
60
60
  * 获取当前组件实例
package/dist/index.esm.js CHANGED
@@ -615,6 +615,15 @@ function cleanupEmptyEffects(target, type, key, effects, record, subscriber) {
615
615
  subscriber.delete(type);
616
616
  if (subscriber.size === 0) subscribers.delete(target);
617
617
  }
618
+ /** @internal 是否有订阅者监听 target 上某一追踪键(用于 computed 等按需刷新) */
619
+ function hasEffectSubscribers(target, type, key = unKnownKey) {
620
+ const subscriber = subscribers.get(target);
621
+ if (!subscriber) return false;
622
+ const record = subscriber.get(type);
623
+ if (!record) return false;
624
+ const effects = record.get(key);
625
+ return !!effects && effects.size > 0;
626
+ }
618
627
  function track(target, type, key = unKnownKey) {
619
628
  const dep = getDepContext();
620
629
  if (dep) {
@@ -1466,10 +1475,9 @@ var Component = class {
1466
1475
  constructor(parentComponent, type, props, key) {
1467
1476
  this.parentComponent = parentComponent;
1468
1477
  this.type = type;
1469
- this.props = props;
1470
1478
  this.key = key;
1471
1479
  this.instance = null;
1472
- this.changedSubComponents = /* @__PURE__ */ new Set();
1480
+ this.changedSubComponents = null;
1473
1481
  this.viewMetadata = null;
1474
1482
  this.unmountedCallbacks = null;
1475
1483
  this.mountCallbacks = null;
@@ -1480,7 +1488,7 @@ var Component = class {
1480
1488
  this.isFirstRendering = true;
1481
1489
  this.rawProps = props;
1482
1490
  this.props = createShallowReadonlyProxy({ ...props });
1483
- this.refEffects = /* @__PURE__ */ new Map();
1491
+ this.refEffects = null;
1484
1492
  this.listener = new Dep(() => {
1485
1493
  this.markAsDirtied();
1486
1494
  }, "sync");
@@ -1490,19 +1498,34 @@ var Component = class {
1490
1498
  this.markAsChanged();
1491
1499
  }
1492
1500
  markAsChanged(changedComponent) {
1493
- if (changedComponent) this.changedSubComponents.add(changedComponent);
1501
+ if (changedComponent) {
1502
+ if (!this.changedSubComponents) this.changedSubComponents = /* @__PURE__ */ new Set();
1503
+ this.changedSubComponents.add(changedComponent);
1504
+ }
1494
1505
  if (this._changed) return;
1495
1506
  this._changed = true;
1496
1507
  if (this.parentComponent) this.parentComponent.markAsChanged(this);
1497
1508
  }
1498
- render(update) {
1509
+ render() {
1510
+ if (this.instance) {
1511
+ this.listener.destroy();
1512
+ pushDepContext(this.listener);
1513
+ const template = this.instance.render();
1514
+ popDepContext();
1515
+ return template;
1516
+ }
1499
1517
  componentSetupStack.push(this);
1500
1518
  const render = this.type(this.props);
1501
1519
  const isRenderFn = typeof render === "function";
1502
1520
  this.instance = isRenderFn ? { render } : render;
1503
1521
  onMounted(() => {
1504
- applyRefs(this.props.ref, this.instance, this.refEffects);
1522
+ const refs = this.props.ref;
1523
+ if (refs) {
1524
+ const refEffects = this.getOrCreateRefEffects();
1525
+ applyRefs(refs, this.instance, refEffects);
1526
+ }
1505
1527
  return () => {
1528
+ if (!this.refEffects) return;
1506
1529
  this.refEffects.forEach((fn) => {
1507
1530
  if (typeof fn === "function") fn();
1508
1531
  });
@@ -1512,14 +1535,16 @@ var Component = class {
1512
1535
  pushDepContext(this.listener);
1513
1536
  const template = this.instance.render();
1514
1537
  popDepContext();
1515
- update(template);
1516
- this.rendered();
1538
+ return template;
1517
1539
  }
1518
1540
  updateProps(newProps) {
1519
1541
  const oldProps = this.rawProps;
1520
1542
  this.rawProps = newProps;
1521
1543
  const newRefs = newProps.ref;
1522
- updateRefs(newRefs, this.instance, this.refEffects);
1544
+ if (oldProps.ref || newRefs) {
1545
+ const refEffects = this.getOrCreateRefEffects();
1546
+ updateRefs(newRefs, this.instance, refEffects);
1547
+ }
1523
1548
  comparePropsWithCallbacks(oldProps, newProps, (key) => {
1524
1549
  internalWrite(() => {
1525
1550
  Reflect.deleteProperty(oldProps, key);
@@ -1534,13 +1559,6 @@ var Component = class {
1534
1559
  });
1535
1560
  });
1536
1561
  }
1537
- rerender() {
1538
- this.listener.destroy();
1539
- pushDepContext(this.listener);
1540
- const template = this.instance.render();
1541
- popDepContext();
1542
- return template;
1543
- }
1544
1562
  destroy() {
1545
1563
  this.listener.destroy();
1546
1564
  if (this.updatedDestroyCallbacks) this.updatedDestroyCallbacks.forEach((fn) => {
@@ -1549,10 +1567,11 @@ var Component = class {
1549
1567
  if (this.unmountedCallbacks) this.unmountedCallbacks.forEach((fn) => {
1550
1568
  fn();
1551
1569
  });
1552
- this.parentComponent = this.updatedDestroyCallbacks = this.mountCallbacks = this.updatedCallbacks = this.unmountedCallbacks = null;
1570
+ this.isFirstRendering = true;
1571
+ this.changedSubComponents = this.refEffects = this.updatedDestroyCallbacks = this.mountCallbacks = this.updatedCallbacks = this.unmountedCallbacks = this.instance = null;
1553
1572
  }
1554
1573
  rendered() {
1555
- this.changedSubComponents.clear();
1574
+ this.changedSubComponents?.clear();
1556
1575
  const is = this.isFirstRendering;
1557
1576
  this.isFirstRendering = false;
1558
1577
  this._dirty = this._changed = false;
@@ -1591,6 +1610,10 @@ var Component = class {
1591
1610
  this.updatedDestroyCallbacks = updatedDestroyCallbacks.length ? updatedDestroyCallbacks : null;
1592
1611
  }
1593
1612
  }
1613
+ getOrCreateRefEffects() {
1614
+ if (!this.refEffects) this.refEffects = /* @__PURE__ */ new Map();
1615
+ return this.refEffects;
1616
+ }
1594
1617
  };
1595
1618
  /**
1596
1619
  * 获取当前组件实例
@@ -1974,23 +1997,20 @@ function buildElementChildren(atom, nativeRenderer, parentComponent, context) {
1974
1997
  }
1975
1998
  }
1976
1999
  function patchComponent(nativeRenderer, component, oldChildAtom, newAtom, context, needMove) {
1977
- const newTemplate = component.rerender();
2000
+ const newTemplate = component.render();
1978
2001
  const portalContainer = getContainer();
1979
2002
  const rawContext = context;
1980
2003
  const { computedContainer, contextContainer } = component.viewMetadata;
1981
2004
  popContainer();
1982
- if (portalContainer) if (portalContainer === context.contextContainer) {
2005
+ if (portalContainer) {
1983
2006
  if (portalContainer !== computedContainer) needMove = true;
1984
- } else {
1985
- if (portalContainer !== computedContainer) needMove = true;
1986
- context = {
2007
+ if (portalContainer !== context.contextContainer) context = {
1987
2008
  isParent: true,
1988
2009
  anchorNode: portalContainer,
1989
2010
  contextContainer: context.contextContainer,
1990
2011
  computedContainer: portalContainer
1991
2012
  };
1992
- }
1993
- else if (contextContainer !== context.contextContainer) needMove = true;
2013
+ } else if (contextContainer !== context.contextContainer) needMove = true;
1994
2014
  component.viewMetadata = {
1995
2015
  atom: newAtom,
1996
2016
  ...context
@@ -2018,9 +2038,8 @@ function deepUpdateByComponentDirtyTree(nativeRenderer, component, needMove) {
2018
2038
  }
2019
2039
  component.rendered();
2020
2040
  } else if (component.changed) {
2021
- component.changedSubComponents.forEach((child) => {
2022
- deepUpdateByComponentDirtyTree(nativeRenderer, child, needMove);
2023
- });
2041
+ const changedSubComponents = component.changedSubComponents;
2042
+ if (changedSubComponents) for (const child of changedSubComponents) deepUpdateByComponentDirtyTree(nativeRenderer, child, needMove);
2024
2043
  component.rendered();
2025
2044
  }
2026
2045
  }
@@ -2128,7 +2147,7 @@ function updateComponent(nativeRenderer, context, offset, needMove, newAtom, old
2128
2147
  ...context
2129
2148
  };
2130
2149
  newAtom.child = oldAtom.child;
2131
- const skipSubComponentDiff = !component.changedSubComponents.size;
2150
+ const skipSubComponentDiff = !component.changedSubComponents?.size;
2132
2151
  reuseComponentView(nativeRenderer, newAtom.child, context, needMove, skipSubComponentDiff);
2133
2152
  if (!skipSubComponentDiff) component.rendered();
2134
2153
  }
@@ -2198,26 +2217,26 @@ function cleanChildren(atom, nativeRenderer, needClean) {
2198
2217
  }
2199
2218
  }
2200
2219
  function componentRender(nativeRenderer, component, from, context) {
2201
- component.render((template) => {
2202
- const portalContainer = getContainer();
2203
- popContainer();
2204
- from.child = createChildChain(template, nativeRenderer, from.namespace);
2205
- if (portalContainer && portalContainer !== context.contextContainer) context = {
2206
- isParent: true,
2207
- anchorNode: portalContainer,
2208
- contextContainer: context.contextContainer,
2209
- computedContainer: portalContainer
2210
- };
2211
- component.viewMetadata = {
2212
- atom: from,
2213
- ...context
2214
- };
2215
- let child = from.child;
2216
- while (child) {
2217
- buildView(nativeRenderer, component, child, context);
2218
- child = child.sibling;
2219
- }
2220
- });
2220
+ const template = component.render();
2221
+ const portalContainer = getContainer();
2222
+ popContainer();
2223
+ from.child = createChildChain(template, nativeRenderer, from.namespace);
2224
+ if (portalContainer && portalContainer !== context.contextContainer) context = {
2225
+ isParent: true,
2226
+ anchorNode: portalContainer,
2227
+ contextContainer: context.contextContainer,
2228
+ computedContainer: portalContainer
2229
+ };
2230
+ component.viewMetadata = {
2231
+ atom: from,
2232
+ ...context
2233
+ };
2234
+ let child = from.child;
2235
+ while (child) {
2236
+ buildView(nativeRenderer, component, child, context);
2237
+ child = child.sibling;
2238
+ }
2239
+ component.rendered();
2221
2240
  }
2222
2241
  function createChainByJSXNode(type, jsxNode, nodeType, prevAtom, namespace, key) {
2223
2242
  const atom = {
@@ -2457,7 +2476,10 @@ var RootComponent = class extends Component {
2457
2476
  }
2458
2477
  markAsChanged(changedComponent) {
2459
2478
  this._changed = true;
2460
- if (changedComponent) this.changedSubComponents.add(changedComponent);
2479
+ if (changedComponent) {
2480
+ if (!this.changedSubComponents) this.changedSubComponents = /* @__PURE__ */ new Set();
2481
+ this.changedSubComponents.add(changedComponent);
2482
+ }
2461
2483
  this.refresh();
2462
2484
  }
2463
2485
  };
@@ -2534,29 +2556,39 @@ function viewfly(config) {
2534
2556
  /**
2535
2557
  * 创建一个 computed,当依赖的值发生变化时,会重新计算值。
2536
2558
  * computed 会返回一个对象,对象的 value 属性是计算的值。
2559
+ *
2560
+ * 若重新计算的结果与缓存值 `Object.is` 相等,不会对访问该 computed 的订阅者触发更新,
2561
+ * 避免无关依赖抖动导致的下游副作用。
2562
+ *
2537
2563
  * @param getter 计算函数,用于计算值
2538
2564
  * @returns 一个对象,对象的 value 属性是计算的值
2539
2565
  */
2540
2566
  function computed(getter) {
2541
2567
  let cacheValue;
2568
+ let hasCache = false;
2542
2569
  let dirty = true;
2543
2570
  const target = { get value() {
2544
- if (dirty) {
2545
- dep.destroy();
2546
- pushDepContext(dep);
2547
- try {
2548
- cacheValue = getter();
2549
- dirty = false;
2550
- } finally {
2551
- popDepContext();
2552
- }
2553
- }
2571
+ if (dirty) flushComputed(false);
2554
2572
  return cacheValue;
2555
2573
  } };
2574
+ function flushComputed(notify) {
2575
+ dep.destroy();
2576
+ pushDepContext(dep);
2577
+ try {
2578
+ const newValue = getter();
2579
+ const changed = !hasCache || !Object.is(newValue, cacheValue);
2580
+ hasCache = true;
2581
+ cacheValue = newValue;
2582
+ dirty = false;
2583
+ if (notify && changed) trigger(target, TriggerOpTypes.Set, "value");
2584
+ } finally {
2585
+ popDepContext();
2586
+ }
2587
+ }
2556
2588
  const dep = new Dep(() => {
2557
2589
  if (!dirty) {
2558
2590
  dirty = true;
2559
- trigger(target, TriggerOpTypes.Set, "value");
2591
+ if (hasEffectSubscribers(target, TrackOpTypes.Get, "value")) flushComputed(true);
2560
2592
  }
2561
2593
  }, "sync");
2562
2594
  registryComponentDestroyCallback(() => {
@@ -2643,4 +2675,4 @@ function createSignal(state) {
2643
2675
  return signal;
2644
2676
  }
2645
2677
  //#endregion
2646
- export { ArrayReactiveHandler, Component, Dep, ForwardRef, Fragment, Inject, InjectFlags, Injectable, InjectionToken, Injector, JSXNodeFactory, MapReactiveHandler, NativeRenderer, NullInjector, ObjectReactiveHandler, Optional, Portal, Prop, ReflectiveInjector, RootComponent, Scope, Self, SetReactiveHandler, SkipSelf, THROW_IF_NOT_FOUND, TrackOpTypes, TriggerOpTypes, Type, applyMark, applyRefs, comparePropsWithCallbacks, computed, createContext, createContextProvider, createDynamicRef, createRef, createRenderer, createShallowReadonlyProxy, createSignal, defaultArrayReactiveHandler, defaultMapReactiveHandler, defaultObjectReactiveHandler, defaultSetReactiveHandler, defaultShallowArrayReactiveHandler, defaultShallowMapReactiveHandler, defaultShallowObjectReactiveHandler, defaultShallowSetReactiveHandler, flushReactiveEffectsSync, forwardRef, getCurrentInstance, getDepContext, getSetupContext, inject, internalWrite, isReactive, jsx, jsxs, makeError, nextTick, normalizeProvider, onMounted, onUnmounted, onUpdated, popDepContext, proxyToRawCache, pushDepContext, rawToProxyCache, reactive, readonlyProxyHandler, registryComponentDestroyCallback, shallowProxyToRawCache, shallowRawToProxyCache, shallowReactive, toRaw, track, trigger, updateRefs, viewfly, watch, watchEffect, withAnnotation, withMark };
2678
+ export { ArrayReactiveHandler, Component, Dep, ForwardRef, Fragment, Inject, InjectFlags, Injectable, InjectionToken, Injector, JSXNodeFactory, MapReactiveHandler, NativeRenderer, NullInjector, ObjectReactiveHandler, Optional, Portal, Prop, ReflectiveInjector, RootComponent, Scope, Self, SetReactiveHandler, SkipSelf, THROW_IF_NOT_FOUND, TrackOpTypes, TriggerOpTypes, Type, applyMark, applyRefs, comparePropsWithCallbacks, computed, createContext, createContextProvider, createDynamicRef, createRef, createRenderer, createShallowReadonlyProxy, createSignal, defaultArrayReactiveHandler, defaultMapReactiveHandler, defaultObjectReactiveHandler, defaultSetReactiveHandler, defaultShallowArrayReactiveHandler, defaultShallowMapReactiveHandler, defaultShallowObjectReactiveHandler, defaultShallowSetReactiveHandler, flushReactiveEffectsSync, forwardRef, getCurrentInstance, getDepContext, getSetupContext, hasEffectSubscribers, inject, internalWrite, isReactive, jsx, jsxs, makeError, nextTick, normalizeProvider, onMounted, onUnmounted, onUpdated, popDepContext, proxyToRawCache, pushDepContext, rawToProxyCache, reactive, readonlyProxyHandler, registryComponentDestroyCallback, shallowProxyToRawCache, shallowRawToProxyCache, shallowReactive, toRaw, track, trigger, updateRefs, viewfly, watch, watchEffect, withAnnotation, withMark };
package/dist/index.js CHANGED
@@ -616,6 +616,15 @@ function cleanupEmptyEffects(target, type, key, effects, record, subscriber) {
616
616
  subscriber.delete(type);
617
617
  if (subscriber.size === 0) subscribers.delete(target);
618
618
  }
619
+ /** @internal 是否有订阅者监听 target 上某一追踪键(用于 computed 等按需刷新) */
620
+ function hasEffectSubscribers(target, type, key = unKnownKey) {
621
+ const subscriber = subscribers.get(target);
622
+ if (!subscriber) return false;
623
+ const record = subscriber.get(type);
624
+ if (!record) return false;
625
+ const effects = record.get(key);
626
+ return !!effects && effects.size > 0;
627
+ }
619
628
  function track(target, type, key = unKnownKey) {
620
629
  const dep = getDepContext();
621
630
  if (dep) {
@@ -1467,10 +1476,9 @@ var Component = class {
1467
1476
  constructor(parentComponent, type, props, key) {
1468
1477
  this.parentComponent = parentComponent;
1469
1478
  this.type = type;
1470
- this.props = props;
1471
1479
  this.key = key;
1472
1480
  this.instance = null;
1473
- this.changedSubComponents = /* @__PURE__ */ new Set();
1481
+ this.changedSubComponents = null;
1474
1482
  this.viewMetadata = null;
1475
1483
  this.unmountedCallbacks = null;
1476
1484
  this.mountCallbacks = null;
@@ -1481,7 +1489,7 @@ var Component = class {
1481
1489
  this.isFirstRendering = true;
1482
1490
  this.rawProps = props;
1483
1491
  this.props = createShallowReadonlyProxy({ ...props });
1484
- this.refEffects = /* @__PURE__ */ new Map();
1492
+ this.refEffects = null;
1485
1493
  this.listener = new Dep(() => {
1486
1494
  this.markAsDirtied();
1487
1495
  }, "sync");
@@ -1491,19 +1499,34 @@ var Component = class {
1491
1499
  this.markAsChanged();
1492
1500
  }
1493
1501
  markAsChanged(changedComponent) {
1494
- if (changedComponent) this.changedSubComponents.add(changedComponent);
1502
+ if (changedComponent) {
1503
+ if (!this.changedSubComponents) this.changedSubComponents = /* @__PURE__ */ new Set();
1504
+ this.changedSubComponents.add(changedComponent);
1505
+ }
1495
1506
  if (this._changed) return;
1496
1507
  this._changed = true;
1497
1508
  if (this.parentComponent) this.parentComponent.markAsChanged(this);
1498
1509
  }
1499
- render(update) {
1510
+ render() {
1511
+ if (this.instance) {
1512
+ this.listener.destroy();
1513
+ pushDepContext(this.listener);
1514
+ const template = this.instance.render();
1515
+ popDepContext();
1516
+ return template;
1517
+ }
1500
1518
  componentSetupStack.push(this);
1501
1519
  const render = this.type(this.props);
1502
1520
  const isRenderFn = typeof render === "function";
1503
1521
  this.instance = isRenderFn ? { render } : render;
1504
1522
  onMounted(() => {
1505
- applyRefs(this.props.ref, this.instance, this.refEffects);
1523
+ const refs = this.props.ref;
1524
+ if (refs) {
1525
+ const refEffects = this.getOrCreateRefEffects();
1526
+ applyRefs(refs, this.instance, refEffects);
1527
+ }
1506
1528
  return () => {
1529
+ if (!this.refEffects) return;
1507
1530
  this.refEffects.forEach((fn) => {
1508
1531
  if (typeof fn === "function") fn();
1509
1532
  });
@@ -1513,14 +1536,16 @@ var Component = class {
1513
1536
  pushDepContext(this.listener);
1514
1537
  const template = this.instance.render();
1515
1538
  popDepContext();
1516
- update(template);
1517
- this.rendered();
1539
+ return template;
1518
1540
  }
1519
1541
  updateProps(newProps) {
1520
1542
  const oldProps = this.rawProps;
1521
1543
  this.rawProps = newProps;
1522
1544
  const newRefs = newProps.ref;
1523
- updateRefs(newRefs, this.instance, this.refEffects);
1545
+ if (oldProps.ref || newRefs) {
1546
+ const refEffects = this.getOrCreateRefEffects();
1547
+ updateRefs(newRefs, this.instance, refEffects);
1548
+ }
1524
1549
  comparePropsWithCallbacks(oldProps, newProps, (key) => {
1525
1550
  internalWrite(() => {
1526
1551
  Reflect.deleteProperty(oldProps, key);
@@ -1535,13 +1560,6 @@ var Component = class {
1535
1560
  });
1536
1561
  });
1537
1562
  }
1538
- rerender() {
1539
- this.listener.destroy();
1540
- pushDepContext(this.listener);
1541
- const template = this.instance.render();
1542
- popDepContext();
1543
- return template;
1544
- }
1545
1563
  destroy() {
1546
1564
  this.listener.destroy();
1547
1565
  if (this.updatedDestroyCallbacks) this.updatedDestroyCallbacks.forEach((fn) => {
@@ -1550,10 +1568,11 @@ var Component = class {
1550
1568
  if (this.unmountedCallbacks) this.unmountedCallbacks.forEach((fn) => {
1551
1569
  fn();
1552
1570
  });
1553
- this.parentComponent = this.updatedDestroyCallbacks = this.mountCallbacks = this.updatedCallbacks = this.unmountedCallbacks = null;
1571
+ this.isFirstRendering = true;
1572
+ this.changedSubComponents = this.refEffects = this.updatedDestroyCallbacks = this.mountCallbacks = this.updatedCallbacks = this.unmountedCallbacks = this.instance = null;
1554
1573
  }
1555
1574
  rendered() {
1556
- this.changedSubComponents.clear();
1575
+ this.changedSubComponents?.clear();
1557
1576
  const is = this.isFirstRendering;
1558
1577
  this.isFirstRendering = false;
1559
1578
  this._dirty = this._changed = false;
@@ -1592,6 +1611,10 @@ var Component = class {
1592
1611
  this.updatedDestroyCallbacks = updatedDestroyCallbacks.length ? updatedDestroyCallbacks : null;
1593
1612
  }
1594
1613
  }
1614
+ getOrCreateRefEffects() {
1615
+ if (!this.refEffects) this.refEffects = /* @__PURE__ */ new Map();
1616
+ return this.refEffects;
1617
+ }
1595
1618
  };
1596
1619
  /**
1597
1620
  * 获取当前组件实例
@@ -1975,23 +1998,20 @@ function buildElementChildren(atom, nativeRenderer, parentComponent, context) {
1975
1998
  }
1976
1999
  }
1977
2000
  function patchComponent(nativeRenderer, component, oldChildAtom, newAtom, context, needMove) {
1978
- const newTemplate = component.rerender();
2001
+ const newTemplate = component.render();
1979
2002
  const portalContainer = getContainer();
1980
2003
  const rawContext = context;
1981
2004
  const { computedContainer, contextContainer } = component.viewMetadata;
1982
2005
  popContainer();
1983
- if (portalContainer) if (portalContainer === context.contextContainer) {
2006
+ if (portalContainer) {
1984
2007
  if (portalContainer !== computedContainer) needMove = true;
1985
- } else {
1986
- if (portalContainer !== computedContainer) needMove = true;
1987
- context = {
2008
+ if (portalContainer !== context.contextContainer) context = {
1988
2009
  isParent: true,
1989
2010
  anchorNode: portalContainer,
1990
2011
  contextContainer: context.contextContainer,
1991
2012
  computedContainer: portalContainer
1992
2013
  };
1993
- }
1994
- else if (contextContainer !== context.contextContainer) needMove = true;
2014
+ } else if (contextContainer !== context.contextContainer) needMove = true;
1995
2015
  component.viewMetadata = {
1996
2016
  atom: newAtom,
1997
2017
  ...context
@@ -2019,9 +2039,8 @@ function deepUpdateByComponentDirtyTree(nativeRenderer, component, needMove) {
2019
2039
  }
2020
2040
  component.rendered();
2021
2041
  } else if (component.changed) {
2022
- component.changedSubComponents.forEach((child) => {
2023
- deepUpdateByComponentDirtyTree(nativeRenderer, child, needMove);
2024
- });
2042
+ const changedSubComponents = component.changedSubComponents;
2043
+ if (changedSubComponents) for (const child of changedSubComponents) deepUpdateByComponentDirtyTree(nativeRenderer, child, needMove);
2025
2044
  component.rendered();
2026
2045
  }
2027
2046
  }
@@ -2129,7 +2148,7 @@ function updateComponent(nativeRenderer, context, offset, needMove, newAtom, old
2129
2148
  ...context
2130
2149
  };
2131
2150
  newAtom.child = oldAtom.child;
2132
- const skipSubComponentDiff = !component.changedSubComponents.size;
2151
+ const skipSubComponentDiff = !component.changedSubComponents?.size;
2133
2152
  reuseComponentView(nativeRenderer, newAtom.child, context, needMove, skipSubComponentDiff);
2134
2153
  if (!skipSubComponentDiff) component.rendered();
2135
2154
  }
@@ -2199,26 +2218,26 @@ function cleanChildren(atom, nativeRenderer, needClean) {
2199
2218
  }
2200
2219
  }
2201
2220
  function componentRender(nativeRenderer, component, from, context) {
2202
- component.render((template) => {
2203
- const portalContainer = getContainer();
2204
- popContainer();
2205
- from.child = createChildChain(template, nativeRenderer, from.namespace);
2206
- if (portalContainer && portalContainer !== context.contextContainer) context = {
2207
- isParent: true,
2208
- anchorNode: portalContainer,
2209
- contextContainer: context.contextContainer,
2210
- computedContainer: portalContainer
2211
- };
2212
- component.viewMetadata = {
2213
- atom: from,
2214
- ...context
2215
- };
2216
- let child = from.child;
2217
- while (child) {
2218
- buildView(nativeRenderer, component, child, context);
2219
- child = child.sibling;
2220
- }
2221
- });
2221
+ const template = component.render();
2222
+ const portalContainer = getContainer();
2223
+ popContainer();
2224
+ from.child = createChildChain(template, nativeRenderer, from.namespace);
2225
+ if (portalContainer && portalContainer !== context.contextContainer) context = {
2226
+ isParent: true,
2227
+ anchorNode: portalContainer,
2228
+ contextContainer: context.contextContainer,
2229
+ computedContainer: portalContainer
2230
+ };
2231
+ component.viewMetadata = {
2232
+ atom: from,
2233
+ ...context
2234
+ };
2235
+ let child = from.child;
2236
+ while (child) {
2237
+ buildView(nativeRenderer, component, child, context);
2238
+ child = child.sibling;
2239
+ }
2240
+ component.rendered();
2222
2241
  }
2223
2242
  function createChainByJSXNode(type, jsxNode, nodeType, prevAtom, namespace, key) {
2224
2243
  const atom = {
@@ -2458,7 +2477,10 @@ var RootComponent = class extends Component {
2458
2477
  }
2459
2478
  markAsChanged(changedComponent) {
2460
2479
  this._changed = true;
2461
- if (changedComponent) this.changedSubComponents.add(changedComponent);
2480
+ if (changedComponent) {
2481
+ if (!this.changedSubComponents) this.changedSubComponents = /* @__PURE__ */ new Set();
2482
+ this.changedSubComponents.add(changedComponent);
2483
+ }
2462
2484
  this.refresh();
2463
2485
  }
2464
2486
  };
@@ -2535,29 +2557,39 @@ function viewfly(config) {
2535
2557
  /**
2536
2558
  * 创建一个 computed,当依赖的值发生变化时,会重新计算值。
2537
2559
  * computed 会返回一个对象,对象的 value 属性是计算的值。
2560
+ *
2561
+ * 若重新计算的结果与缓存值 `Object.is` 相等,不会对访问该 computed 的订阅者触发更新,
2562
+ * 避免无关依赖抖动导致的下游副作用。
2563
+ *
2538
2564
  * @param getter 计算函数,用于计算值
2539
2565
  * @returns 一个对象,对象的 value 属性是计算的值
2540
2566
  */
2541
2567
  function computed(getter) {
2542
2568
  let cacheValue;
2569
+ let hasCache = false;
2543
2570
  let dirty = true;
2544
2571
  const target = { get value() {
2545
- if (dirty) {
2546
- dep.destroy();
2547
- pushDepContext(dep);
2548
- try {
2549
- cacheValue = getter();
2550
- dirty = false;
2551
- } finally {
2552
- popDepContext();
2553
- }
2554
- }
2572
+ if (dirty) flushComputed(false);
2555
2573
  return cacheValue;
2556
2574
  } };
2575
+ function flushComputed(notify) {
2576
+ dep.destroy();
2577
+ pushDepContext(dep);
2578
+ try {
2579
+ const newValue = getter();
2580
+ const changed = !hasCache || !Object.is(newValue, cacheValue);
2581
+ hasCache = true;
2582
+ cacheValue = newValue;
2583
+ dirty = false;
2584
+ if (notify && changed) trigger(target, TriggerOpTypes.Set, "value");
2585
+ } finally {
2586
+ popDepContext();
2587
+ }
2588
+ }
2557
2589
  const dep = new Dep(() => {
2558
2590
  if (!dirty) {
2559
2591
  dirty = true;
2560
- trigger(target, TriggerOpTypes.Set, "value");
2592
+ if (hasEffectSubscribers(target, TrackOpTypes.Get, "value")) flushComputed(true);
2561
2593
  }
2562
2594
  }, "sync");
2563
2595
  registryComponentDestroyCallback(() => {
@@ -2696,6 +2728,7 @@ exports.forwardRef = forwardRef;
2696
2728
  exports.getCurrentInstance = getCurrentInstance;
2697
2729
  exports.getDepContext = getDepContext;
2698
2730
  exports.getSetupContext = getSetupContext;
2731
+ exports.hasEffectSubscribers = hasEffectSubscribers;
2699
2732
  exports.inject = inject;
2700
2733
  exports.internalWrite = internalWrite;
2701
2734
  exports.isReactive = isReactive;
@@ -4,6 +4,10 @@ export interface Computed<T> {
4
4
  /**
5
5
  * 创建一个 computed,当依赖的值发生变化时,会重新计算值。
6
6
  * computed 会返回一个对象,对象的 value 属性是计算的值。
7
+ *
8
+ * 若重新计算的结果与缓存值 `Object.is` 相等,不会对访问该 computed 的订阅者触发更新,
9
+ * 避免无关依赖抖动导致的下游副作用。
10
+ *
7
11
  * @param getter 计算函数,用于计算值
8
12
  * @returns 一个对象,对象的 value 属性是计算的值
9
13
  */
@@ -16,5 +16,7 @@ export declare enum TriggerOpTypes {
16
16
  Clear = "Clear",
17
17
  Iterate = "Iterate"
18
18
  }
19
+ /** @internal 是否有订阅者监听 target 上某一追踪键(用于 computed 等按需刷新) */
20
+ export declare function hasEffectSubscribers(target: object, type: TrackOpTypes, key?: unknown): boolean;
19
21
  export declare function track(target: object, type: TrackOpTypes, key?: unknown): void;
20
22
  export declare function trigger(target: object, type: TriggerOpTypes, key?: unknown): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viewfly/core",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "Viewfly is a simple and easy-to-use JavaScript framework with an intuitive development experience.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.esm.js",