Module: lib/core/reactive/scopeProxy

The evaluation scope handed to template expressions, computed values and action bodies.

What was wrong with building it as an object

The scope used to be assembled with a spread:

const scope = { state: this.state, ...this.bridges, ...this.state, ...methods, … };

...this.state reads every key on the reactive proxy, eagerly, every time a scope is built — which is once per render, per action call and per computed evaluation. Three separate defects followed from that one line.

Bare identifiers were not reactive. The documented computed form <computed value="count * 2" /> reads count out of the snapshot the spread produced, not out of the proxy, so no dependency was registered for the expression itself. state.count * 2 worked; the form in the README, quickstart and state-management guide did not, and failed silently — the first render was correct and the value then never changed again.

Computed values reported false cycles. Spreading the state proxy reads the computed key currently being evaluated. That re-entered the computed's own watcher mid-collection, tripping the re-entrancy guard: every app using computed properties logged AVX_R04 Circular dependency detected on every re-render, for a cycle that did not exist. Worse, the re-entrant watcher.evaluate() reset the watcher's dependency set part-way through collection, which is what actually lost the count dependency above. The bogus warning and the missing dependency were the same event.

Reactivity was component-granular. Because the spread touched every key, the render watcher depended on all of them. Changing a key no expression mentioned still re-rendered the component, so the per-key precision the Proxy layer computes was discarded one level above it.

What this does instead

Nothing is read until an expression actually names it. The scope is a Proxy over ordered layers; a get resolves through them and, for a state or computed key, reads the live reactive proxy — inside whichever watcher is currently evaluating. So count in count * 2 registers a dependency on count and on nothing else.

Derivation instead of spreading

Several call sites layer extra names onto a scope — a <@for> loop variable, an event, an action's args. Written as { ...scope, index } that would reintroduce the eager read this module exists to remove, so scopes derive instead: deriveScope pushes another layer without materialising anything. It falls back to a spread for plain objects, so external code that passes an ordinary object still works.

Source:

Members

(static, constant) IS_REACTIVE_SCOPE :symbol

Marks a value as one of this module's scope proxies.

Type:
  • symbol
Source:

(static, constant) SCOPE_DERIVE :symbol

Derives a child scope from a scope proxy.

Type:
  • symbol
Source:

Methods

(static) createReactiveScope(layers) → {object}

Creates the evaluation scope.

Layers are given in precedence order, highest first: the first layer that binds a name wins. That mirrors the object-literal precedence the previous implementation had, where later spreads overwrote earlier ones.

Parameters:
Name Type Description
layers Array:.<ScopeLayer:>

The layers, highest precedence first.

Source:
Returns:

A scope proxy.

Type
object

(static) deriveScope(scope, extras) → {object}

Layers extra bindings on top of an existing scope.

Prefer this over { ...scope, extra } anywhere a scope gains names. A spread of a scope proxy reads every key it can enumerate, which is exactly the eager read this module removes.

Parameters:
Name Type Description
scope object

The scope to extend. May be a scope proxy or a plain object.

extras object

The bindings to layer on top.

Source:
Returns:

A scope with the extras applied.

Type
object

(static) getterLayer(getters) → {ScopeLayer}

Builds a layer of lazily evaluated bindings.

Used for resources, whose value is produced by calling read() and must not be produced merely because a scope was built.

Parameters:
Name Type Description
getters Object:.<string:, function(): any:>

Name to getter.

Source:
Returns:

The layer.

Type
ScopeLayer

(static) isReactiveScope(value) → {boolean}

Reports whether a value is a scope proxy created here.

Parameters:
Name Type Description
value any

The value to test.

Source:
Returns:

True for a reactive scope proxy.

Type
boolean

(static) objectLayer(source) → {ScopeLayer}

Builds a layer over a plain object.

Parameters:
Name Type Description
source object

The object to expose.

Source:
Returns:

The layer.

Type
ScopeLayer

(static) stateLayer(state, listKeys) → {ScopeLayer}

Builds a layer over the component's reactive state.

The names are known without reading anything: they come from the raw target and the compiler's computed list. A value is read from the proxy only when an expression asks for it, which is what makes the read land inside the watcher that is currently evaluating.

Parameters:
Name Type Description
state object

The reactive state proxy.

listKeys function

Returns the bound names.

Source:
Returns:

The layer.

Type
ScopeLayer

(inner) get(key) → {any}

Parameters:
Name Type Description
key string

The name to read.

Source:
Returns:

The bound value.

Type
any

(inner) get(key) → {any}

Parameters:
Name Type Description
key string

The name to read.

Source:
Returns:

The value, read through the reactive proxy.

Type
any

(inner) get(key) → {any}

Parameters:
Name Type Description
key string

The name to read.

Source:
Returns:

The getter's result.

Type
any

(inner) has(key) → {boolean}

Parameters:
Name Type Description
key string

The name to test.

Source:
Returns:

Whether the object binds it.

Type
boolean

(inner) has(key) → {boolean}

Parameters:
Name Type Description
key string

The name to test.

Source:
Returns:

Whether state binds it.

Type
boolean

(inner) has(key) → {boolean}

Parameters:
Name Type Description
key string

The name to test.

Source:
Returns:

Whether a getter is bound.

Type
boolean

(inner) keys() → {Array:.<string:>}

Source:
Returns:

The names bound.

Type
Array:.<string:>

(inner) keys() → {Array:.<string:>}

Source:
Returns:

The names bound.

Type
Array:.<string:>

(inner) keys() → {Array:.<string:>}

Source:
Returns:

The names bound.

Type
Array:.<string:>

(inner) set(key, value) → {boolean}

Parameters:
Name Type Description
key string

The name to write.

value any

The value to assign.

Source:
Returns:

Always true.

Type
boolean

(inner) set(key, value) → {boolean}

Parameters:
Name Type Description
key string

The name to write.

value any

The value to assign.

Source:
Returns:

Always true.

Type
boolean

Type Definitions

ScopeLayer

Type:
  • object
Properties:
Name Type Attributes Description
has function

Whether the layer binds a name.

get function

The value bound to a name.

set function <optional>

Assigns to a bound name.

keys function

The names the layer binds.

Source: