@viewfly/core 3.0.0-alpha.8 → 3.0.1

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
@@ -1,6 +1,6 @@
1
1
  # @viewfly/core
2
2
 
3
- Viewfly 的**内核包**:函数组件、JSX、响应式与信号、`watch`、生命周期、`inject` 与 IoC 相关 API、以及 **`withMark`** 等与 UI 逻辑直接相关的入口均由此包提供。
3
+ Viewfly 的**内核包**:函数组件、`JSX`、响应式与 `signal`、`watch`、生命周期、`inject` 与 IoC 相关 API、以及 **`withMark`** 等与 UI 逻辑直接相关的入口均由此包提供。
4
4
 
5
5
  在浏览器里挂载应用请使用 **`@viewfly/platform-browser`** 的 **`createApp`**(其内部使用本包提供的应用模型)。
6
6
 
@@ -9,8 +9,7 @@ Viewfly 的**内核包**:函数组件、JSX、响应式与信号、`watch`、
9
9
  ## 安装
10
10
 
11
11
  ```bash
12
- pnpm add @viewfly/core
13
- # 或 npm / yarn
12
+ npm install @viewfly/core
14
13
  ```
15
14
 
16
15
  本包依赖 **`reflect-metadata`**。正常从 **`@viewfly/core`** 的主入口导入时,会随模块加载一并初始化;若你的打包拆包方式导致依赖注入相关运行时异常,可在应用入口**最先**增加显式导入:
@@ -19,7 +18,7 @@ pnpm add @viewfly/core
19
18
  import 'reflect-metadata'
20
19
  ```
21
20
 
22
- 更稳妥的做法以**当前安装版本**随附的类型定义与 README 为准。
21
+ 更稳妥的做法以**当前安装版本**随附的类型定义与官方文档(<https://viewfly.org>)为准。
23
22
 
24
23
  ---
25
24
 
@@ -49,7 +48,7 @@ import 'reflect-metadata'
49
48
  | 组件 | 函数组件:在 JSX 中当标签使用;配合生命周期钩子使用。 |
50
49
  | 生命周期 | **`onMounted`**、**`onUpdated`**、**`onUnmounted`** 等(须在组件 setup 阶段调用)。 |
51
50
  | 响应式 | **`reactive`**、**`shallowReactive`**、**`watch`** 等(`reactive` 模块)。 |
52
- | 信号 / 派生 | **`createSignal`**、**`computed`** 等(见 `reactive` 导出;具体符号以类型为准)。 |
51
+ | `signal` / 派生 | **`createSignal`**、**`computed`** 等(见 `reactive` 导出;具体符号以类型为准)。 |
53
52
  | 依赖注入 | 在组件内用 **`inject`** 解析 token;用 **`Injectable()`** 声明可注入类;用 **`withAnnotation`** 或 **`createContext`** / **`createContextProvider`** 挂载 **`Provider`**;根应用上通过 **`createApp(...).provide(...)`**(由 **`@viewfly/platform-browser`** 提供)注册全局提供者。 |
54
53
  | 自定义 DOM 属性标记 | **`withMark(marks, setup)`**:为组件渲染出的元素附加与 `marks` 同名的属性(常用于 scoped CSS 的 `scopeId` 等场景)。 |
55
54
 
@@ -59,8 +58,9 @@ import 'reflect-metadata'
59
58
 
60
59
  ## 文档与示例
61
60
 
62
- - **权威说明**:本文件、源码注释与发布包中的 **`.d.ts`**(第三方文档站点可能滞后)。
63
- - **本仓库试跑**:仓库根目录执行 `pnpm dev` 打开 playground
61
+ - **官方文档**:<https://viewfly.org>(重点见安装、组件、响应式、依赖注入)。
62
+ - **类型与注释**:发布包中的 **`.d.ts`** 与源码注释可用于补充 API 细节。
63
+ - **本仓库试跑**:克隆仓库后使用 **pnpm** 安装依赖,在仓库根目录执行 `pnpm dev` 打开 playground。
64
64
 
65
65
  ---
66
66
 
@@ -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
  /**
@@ -55,6 +55,7 @@ export declare class Component {
55
55
  rendered(): void;
56
56
  private invokeMountHooks;
57
57
  private invokeUpdatedHooks;
58
+ private getOrCreateRefEffects;
58
59
  }
59
60
  /**
60
61
  * 获取当前组件实例
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) {
@@ -1469,7 +1478,7 @@ var Component = class {
1469
1478
  this.props = props;
1470
1479
  this.key = key;
1471
1480
  this.instance = null;
1472
- this.changedSubComponents = /* @__PURE__ */ new Set();
1481
+ this.changedSubComponents = null;
1473
1482
  this.viewMetadata = null;
1474
1483
  this.unmountedCallbacks = null;
1475
1484
  this.mountCallbacks = null;
@@ -1480,7 +1489,7 @@ var Component = class {
1480
1489
  this.isFirstRendering = true;
1481
1490
  this.rawProps = props;
1482
1491
  this.props = createShallowReadonlyProxy({ ...props });
1483
- this.refEffects = /* @__PURE__ */ new Map();
1492
+ this.refEffects = null;
1484
1493
  this.listener = new Dep(() => {
1485
1494
  this.markAsDirtied();
1486
1495
  }, "sync");
@@ -1490,7 +1499,10 @@ var Component = class {
1490
1499
  this.markAsChanged();
1491
1500
  }
1492
1501
  markAsChanged(changedComponent) {
1493
- if (changedComponent) this.changedSubComponents.add(changedComponent);
1502
+ if (changedComponent) {
1503
+ if (!this.changedSubComponents) this.changedSubComponents = /* @__PURE__ */ new Set();
1504
+ this.changedSubComponents.add(changedComponent);
1505
+ }
1494
1506
  if (this._changed) return;
1495
1507
  this._changed = true;
1496
1508
  if (this.parentComponent) this.parentComponent.markAsChanged(this);
@@ -1501,8 +1513,13 @@ var Component = class {
1501
1513
  const isRenderFn = typeof render === "function";
1502
1514
  this.instance = isRenderFn ? { render } : render;
1503
1515
  onMounted(() => {
1504
- applyRefs(this.props.ref, this.instance, this.refEffects);
1516
+ const refs = this.props.ref;
1517
+ if (refs) {
1518
+ const refEffects = this.getOrCreateRefEffects();
1519
+ applyRefs(refs, this.instance, refEffects);
1520
+ }
1505
1521
  return () => {
1522
+ if (!this.refEffects) return;
1506
1523
  this.refEffects.forEach((fn) => {
1507
1524
  if (typeof fn === "function") fn();
1508
1525
  });
@@ -1519,7 +1536,10 @@ var Component = class {
1519
1536
  const oldProps = this.rawProps;
1520
1537
  this.rawProps = newProps;
1521
1538
  const newRefs = newProps.ref;
1522
- updateRefs(newRefs, this.instance, this.refEffects);
1539
+ if (oldProps.ref || newRefs) {
1540
+ const refEffects = this.getOrCreateRefEffects();
1541
+ updateRefs(newRefs, this.instance, refEffects);
1542
+ }
1523
1543
  comparePropsWithCallbacks(oldProps, newProps, (key) => {
1524
1544
  internalWrite(() => {
1525
1545
  Reflect.deleteProperty(oldProps, key);
@@ -1549,10 +1569,10 @@ var Component = class {
1549
1569
  if (this.unmountedCallbacks) this.unmountedCallbacks.forEach((fn) => {
1550
1570
  fn();
1551
1571
  });
1552
- this.parentComponent = this.updatedDestroyCallbacks = this.mountCallbacks = this.updatedCallbacks = this.unmountedCallbacks = null;
1572
+ this.parentComponent = this.changedSubComponents = this.refEffects = this.updatedDestroyCallbacks = this.mountCallbacks = this.updatedCallbacks = this.unmountedCallbacks = null;
1553
1573
  }
1554
1574
  rendered() {
1555
- this.changedSubComponents.clear();
1575
+ this.changedSubComponents?.clear();
1556
1576
  const is = this.isFirstRendering;
1557
1577
  this.isFirstRendering = false;
1558
1578
  this._dirty = this._changed = false;
@@ -1591,6 +1611,10 @@ var Component = class {
1591
1611
  this.updatedDestroyCallbacks = updatedDestroyCallbacks.length ? updatedDestroyCallbacks : null;
1592
1612
  }
1593
1613
  }
1614
+ getOrCreateRefEffects() {
1615
+ if (!this.refEffects) this.refEffects = /* @__PURE__ */ new Map();
1616
+ return this.refEffects;
1617
+ }
1594
1618
  };
1595
1619
  /**
1596
1620
  * 获取当前组件实例
@@ -1977,24 +2001,20 @@ function patchComponent(nativeRenderer, component, oldChildAtom, newAtom, contex
1977
2001
  const newTemplate = component.rerender();
1978
2002
  const portalContainer = getContainer();
1979
2003
  const rawContext = context;
1980
- const { computedContainer } = component.viewMetadata;
2004
+ const { computedContainer, contextContainer } = component.viewMetadata;
1981
2005
  popContainer();
1982
- if (portalContainer) if (portalContainer === context.contextContainer) {
2006
+ if (portalContainer) {
1983
2007
  if (portalContainer !== computedContainer) needMove = true;
1984
- } else {
1985
- if (portalContainer !== computedContainer) needMove = true;
1986
- context = {
2008
+ if (portalContainer !== context.contextContainer) context = {
1987
2009
  isParent: true,
1988
2010
  anchorNode: portalContainer,
1989
- contextContainer: portalContainer,
2011
+ contextContainer: context.contextContainer,
1990
2012
  computedContainer: portalContainer
1991
2013
  };
1992
- }
1993
- else if (computedContainer !== context.contextContainer) needMove = true;
2014
+ } else if (contextContainer !== context.contextContainer) needMove = true;
1994
2015
  component.viewMetadata = {
1995
2016
  atom: newAtom,
1996
- ...context,
1997
- contextContainer: rawContext.contextContainer
2017
+ ...context
1998
2018
  };
1999
2019
  newAtom.child = createChildChain(newTemplate, nativeRenderer, newAtom.namespace);
2000
2020
  diff(nativeRenderer, component, newAtom.child, oldChildAtom, context, needMove);
@@ -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
  }
@@ -2122,15 +2141,17 @@ function updateComponent(nativeRenderer, context, offset, needMove, newAtom, old
2122
2141
  view.anchorNode = updatedContext.anchorNode;
2123
2142
  view.isParent = updatedContext.isParent;
2124
2143
  }
2144
+ component.rendered();
2125
2145
  } else {
2126
2146
  component.viewMetadata = {
2127
2147
  atom: newAtom,
2128
2148
  ...context
2129
2149
  };
2130
2150
  newAtom.child = oldAtom.child;
2131
- reuseComponentView(nativeRenderer, newAtom.child, context, needMove, !component.changedSubComponents.size);
2151
+ const skipSubComponentDiff = !component.changedSubComponents?.size;
2152
+ reuseComponentView(nativeRenderer, newAtom.child, context, needMove, skipSubComponentDiff);
2153
+ if (!skipSubComponentDiff) component.rendered();
2132
2154
  }
2133
- component.rendered();
2134
2155
  }
2135
2156
  function reuseComponentView(nativeRenderer, child, context, moveView, skipSubComponentDiff) {
2136
2157
  const updateContext = (atom) => {
@@ -2456,7 +2477,10 @@ var RootComponent = class extends Component {
2456
2477
  }
2457
2478
  markAsChanged(changedComponent) {
2458
2479
  this._changed = true;
2459
- if (changedComponent) this.changedSubComponents.add(changedComponent);
2480
+ if (changedComponent) {
2481
+ if (!this.changedSubComponents) this.changedSubComponents = /* @__PURE__ */ new Set();
2482
+ this.changedSubComponents.add(changedComponent);
2483
+ }
2460
2484
  this.refresh();
2461
2485
  }
2462
2486
  };
@@ -2533,29 +2557,39 @@ function viewfly(config) {
2533
2557
  /**
2534
2558
  * 创建一个 computed,当依赖的值发生变化时,会重新计算值。
2535
2559
  * computed 会返回一个对象,对象的 value 属性是计算的值。
2560
+ *
2561
+ * 若重新计算的结果与缓存值 `Object.is` 相等,不会对访问该 computed 的订阅者触发更新,
2562
+ * 避免无关依赖抖动导致的下游副作用。
2563
+ *
2536
2564
  * @param getter 计算函数,用于计算值
2537
2565
  * @returns 一个对象,对象的 value 属性是计算的值
2538
2566
  */
2539
2567
  function computed(getter) {
2540
2568
  let cacheValue;
2569
+ let hasCache = false;
2541
2570
  let dirty = true;
2542
2571
  const target = { get value() {
2543
- if (dirty) {
2544
- dep.destroy();
2545
- pushDepContext(dep);
2546
- try {
2547
- cacheValue = getter();
2548
- dirty = false;
2549
- } finally {
2550
- popDepContext();
2551
- }
2552
- }
2572
+ if (dirty) flushComputed(false);
2553
2573
  return cacheValue;
2554
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
+ }
2555
2589
  const dep = new Dep(() => {
2556
2590
  if (!dirty) {
2557
2591
  dirty = true;
2558
- trigger(target, TriggerOpTypes.Set, "value");
2592
+ if (hasEffectSubscribers(target, TrackOpTypes.Get, "value")) flushComputed(true);
2559
2593
  }
2560
2594
  }, "sync");
2561
2595
  registryComponentDestroyCallback(() => {
@@ -2642,4 +2676,4 @@ function createSignal(state) {
2642
2676
  return signal;
2643
2677
  }
2644
2678
  //#endregion
2645
- 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 };
2679
+ 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) {
@@ -1470,7 +1479,7 @@ var Component = class {
1470
1479
  this.props = props;
1471
1480
  this.key = key;
1472
1481
  this.instance = null;
1473
- this.changedSubComponents = /* @__PURE__ */ new Set();
1482
+ this.changedSubComponents = null;
1474
1483
  this.viewMetadata = null;
1475
1484
  this.unmountedCallbacks = null;
1476
1485
  this.mountCallbacks = null;
@@ -1481,7 +1490,7 @@ var Component = class {
1481
1490
  this.isFirstRendering = true;
1482
1491
  this.rawProps = props;
1483
1492
  this.props = createShallowReadonlyProxy({ ...props });
1484
- this.refEffects = /* @__PURE__ */ new Map();
1493
+ this.refEffects = null;
1485
1494
  this.listener = new Dep(() => {
1486
1495
  this.markAsDirtied();
1487
1496
  }, "sync");
@@ -1491,7 +1500,10 @@ var Component = class {
1491
1500
  this.markAsChanged();
1492
1501
  }
1493
1502
  markAsChanged(changedComponent) {
1494
- if (changedComponent) this.changedSubComponents.add(changedComponent);
1503
+ if (changedComponent) {
1504
+ if (!this.changedSubComponents) this.changedSubComponents = /* @__PURE__ */ new Set();
1505
+ this.changedSubComponents.add(changedComponent);
1506
+ }
1495
1507
  if (this._changed) return;
1496
1508
  this._changed = true;
1497
1509
  if (this.parentComponent) this.parentComponent.markAsChanged(this);
@@ -1502,8 +1514,13 @@ var Component = class {
1502
1514
  const isRenderFn = typeof render === "function";
1503
1515
  this.instance = isRenderFn ? { render } : render;
1504
1516
  onMounted(() => {
1505
- applyRefs(this.props.ref, this.instance, this.refEffects);
1517
+ const refs = this.props.ref;
1518
+ if (refs) {
1519
+ const refEffects = this.getOrCreateRefEffects();
1520
+ applyRefs(refs, this.instance, refEffects);
1521
+ }
1506
1522
  return () => {
1523
+ if (!this.refEffects) return;
1507
1524
  this.refEffects.forEach((fn) => {
1508
1525
  if (typeof fn === "function") fn();
1509
1526
  });
@@ -1520,7 +1537,10 @@ var Component = class {
1520
1537
  const oldProps = this.rawProps;
1521
1538
  this.rawProps = newProps;
1522
1539
  const newRefs = newProps.ref;
1523
- updateRefs(newRefs, this.instance, this.refEffects);
1540
+ if (oldProps.ref || newRefs) {
1541
+ const refEffects = this.getOrCreateRefEffects();
1542
+ updateRefs(newRefs, this.instance, refEffects);
1543
+ }
1524
1544
  comparePropsWithCallbacks(oldProps, newProps, (key) => {
1525
1545
  internalWrite(() => {
1526
1546
  Reflect.deleteProperty(oldProps, key);
@@ -1550,10 +1570,10 @@ var Component = class {
1550
1570
  if (this.unmountedCallbacks) this.unmountedCallbacks.forEach((fn) => {
1551
1571
  fn();
1552
1572
  });
1553
- this.parentComponent = this.updatedDestroyCallbacks = this.mountCallbacks = this.updatedCallbacks = this.unmountedCallbacks = null;
1573
+ this.parentComponent = this.changedSubComponents = this.refEffects = this.updatedDestroyCallbacks = this.mountCallbacks = this.updatedCallbacks = this.unmountedCallbacks = null;
1554
1574
  }
1555
1575
  rendered() {
1556
- this.changedSubComponents.clear();
1576
+ this.changedSubComponents?.clear();
1557
1577
  const is = this.isFirstRendering;
1558
1578
  this.isFirstRendering = false;
1559
1579
  this._dirty = this._changed = false;
@@ -1592,6 +1612,10 @@ var Component = class {
1592
1612
  this.updatedDestroyCallbacks = updatedDestroyCallbacks.length ? updatedDestroyCallbacks : null;
1593
1613
  }
1594
1614
  }
1615
+ getOrCreateRefEffects() {
1616
+ if (!this.refEffects) this.refEffects = /* @__PURE__ */ new Map();
1617
+ return this.refEffects;
1618
+ }
1595
1619
  };
1596
1620
  /**
1597
1621
  * 获取当前组件实例
@@ -1978,24 +2002,20 @@ function patchComponent(nativeRenderer, component, oldChildAtom, newAtom, contex
1978
2002
  const newTemplate = component.rerender();
1979
2003
  const portalContainer = getContainer();
1980
2004
  const rawContext = context;
1981
- const { computedContainer } = component.viewMetadata;
2005
+ const { computedContainer, contextContainer } = component.viewMetadata;
1982
2006
  popContainer();
1983
- if (portalContainer) if (portalContainer === context.contextContainer) {
2007
+ if (portalContainer) {
1984
2008
  if (portalContainer !== computedContainer) needMove = true;
1985
- } else {
1986
- if (portalContainer !== computedContainer) needMove = true;
1987
- context = {
2009
+ if (portalContainer !== context.contextContainer) context = {
1988
2010
  isParent: true,
1989
2011
  anchorNode: portalContainer,
1990
- contextContainer: portalContainer,
2012
+ contextContainer: context.contextContainer,
1991
2013
  computedContainer: portalContainer
1992
2014
  };
1993
- }
1994
- else if (computedContainer !== context.contextContainer) needMove = true;
2015
+ } else if (contextContainer !== context.contextContainer) needMove = true;
1995
2016
  component.viewMetadata = {
1996
2017
  atom: newAtom,
1997
- ...context,
1998
- contextContainer: rawContext.contextContainer
2018
+ ...context
1999
2019
  };
2000
2020
  newAtom.child = createChildChain(newTemplate, nativeRenderer, newAtom.namespace);
2001
2021
  diff(nativeRenderer, component, newAtom.child, oldChildAtom, context, needMove);
@@ -2020,9 +2040,8 @@ function deepUpdateByComponentDirtyTree(nativeRenderer, component, needMove) {
2020
2040
  }
2021
2041
  component.rendered();
2022
2042
  } else if (component.changed) {
2023
- component.changedSubComponents.forEach((child) => {
2024
- deepUpdateByComponentDirtyTree(nativeRenderer, child, needMove);
2025
- });
2043
+ const changedSubComponents = component.changedSubComponents;
2044
+ if (changedSubComponents) for (const child of changedSubComponents) deepUpdateByComponentDirtyTree(nativeRenderer, child, needMove);
2026
2045
  component.rendered();
2027
2046
  }
2028
2047
  }
@@ -2123,15 +2142,17 @@ function updateComponent(nativeRenderer, context, offset, needMove, newAtom, old
2123
2142
  view.anchorNode = updatedContext.anchorNode;
2124
2143
  view.isParent = updatedContext.isParent;
2125
2144
  }
2145
+ component.rendered();
2126
2146
  } else {
2127
2147
  component.viewMetadata = {
2128
2148
  atom: newAtom,
2129
2149
  ...context
2130
2150
  };
2131
2151
  newAtom.child = oldAtom.child;
2132
- reuseComponentView(nativeRenderer, newAtom.child, context, needMove, !component.changedSubComponents.size);
2152
+ const skipSubComponentDiff = !component.changedSubComponents?.size;
2153
+ reuseComponentView(nativeRenderer, newAtom.child, context, needMove, skipSubComponentDiff);
2154
+ if (!skipSubComponentDiff) component.rendered();
2133
2155
  }
2134
- component.rendered();
2135
2156
  }
2136
2157
  function reuseComponentView(nativeRenderer, child, context, moveView, skipSubComponentDiff) {
2137
2158
  const updateContext = (atom) => {
@@ -2457,7 +2478,10 @@ var RootComponent = class extends Component {
2457
2478
  }
2458
2479
  markAsChanged(changedComponent) {
2459
2480
  this._changed = true;
2460
- if (changedComponent) this.changedSubComponents.add(changedComponent);
2481
+ if (changedComponent) {
2482
+ if (!this.changedSubComponents) this.changedSubComponents = /* @__PURE__ */ new Set();
2483
+ this.changedSubComponents.add(changedComponent);
2484
+ }
2461
2485
  this.refresh();
2462
2486
  }
2463
2487
  };
@@ -2534,29 +2558,39 @@ function viewfly(config) {
2534
2558
  /**
2535
2559
  * 创建一个 computed,当依赖的值发生变化时,会重新计算值。
2536
2560
  * computed 会返回一个对象,对象的 value 属性是计算的值。
2561
+ *
2562
+ * 若重新计算的结果与缓存值 `Object.is` 相等,不会对访问该 computed 的订阅者触发更新,
2563
+ * 避免无关依赖抖动导致的下游副作用。
2564
+ *
2537
2565
  * @param getter 计算函数,用于计算值
2538
2566
  * @returns 一个对象,对象的 value 属性是计算的值
2539
2567
  */
2540
2568
  function computed(getter) {
2541
2569
  let cacheValue;
2570
+ let hasCache = false;
2542
2571
  let dirty = true;
2543
2572
  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
- }
2573
+ if (dirty) flushComputed(false);
2554
2574
  return cacheValue;
2555
2575
  } };
2576
+ function flushComputed(notify) {
2577
+ dep.destroy();
2578
+ pushDepContext(dep);
2579
+ try {
2580
+ const newValue = getter();
2581
+ const changed = !hasCache || !Object.is(newValue, cacheValue);
2582
+ hasCache = true;
2583
+ cacheValue = newValue;
2584
+ dirty = false;
2585
+ if (notify && changed) trigger(target, TriggerOpTypes.Set, "value");
2586
+ } finally {
2587
+ popDepContext();
2588
+ }
2589
+ }
2556
2590
  const dep = new Dep(() => {
2557
2591
  if (!dirty) {
2558
2592
  dirty = true;
2559
- trigger(target, TriggerOpTypes.Set, "value");
2593
+ if (hasEffectSubscribers(target, TrackOpTypes.Get, "value")) flushComputed(true);
2560
2594
  }
2561
2595
  }, "sync");
2562
2596
  registryComponentDestroyCallback(() => {
@@ -2695,6 +2729,7 @@ exports.forwardRef = forwardRef;
2695
2729
  exports.getCurrentInstance = getCurrentInstance;
2696
2730
  exports.getDepContext = getDepContext;
2697
2731
  exports.getSetupContext = getSetupContext;
2732
+ exports.hasEffectSubscribers = hasEffectSubscribers;
2698
2733
  exports.inject = inject;
2699
2734
  exports.internalWrite = internalWrite;
2700
2735
  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-alpha.8",
3
+ "version": "3.0.1",
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",