Skip to content

Commit ecadfb7

Browse files
committed
Reactivity concepts
1 parent 7bc2140 commit ecadfb7

File tree

12 files changed

+785
-696
lines changed

12 files changed

+785
-696
lines changed

guides/reactivity/composition.ts

Lines changed: 0 additions & 156 deletions
This file was deleted.

guides/reactivity/concepts.md

Lines changed: 0 additions & 76 deletions
This file was deleted.

guides/reactivity/index.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# The Glimmer Reactivity System
2+
3+
## Table of Contents
4+
5+
1. [Tag Composition](./tag-composition.md): The formal composition semantics of Glimmer's tag-based
6+
validation system.
7+
2. [The Fundamental Laws of Reactivity](./laws.md): A definition of Glimmer's reliable and
8+
consistent reactive programming model, and the rules that reactive abstractions must
9+
satisfy in order to safely support this model.
10+
3. [System Phases](./system-phases.md): A description of the phases of the Glimmer execution model:
11+
_action_, _render_, and _idle_, and how the exeuction model supported batched _UI_ updates while
12+
maintaining a _coherent_ data model.
13+
4. [Reactive Abstractions](./reactive-abstractions.md): A description of the implementation of
14+
a number of reactive abstractions, and how they satisfy the laws of reactivity.
15+
16+
### Pseudocode
17+
18+
This directory also contains pseudocode for the foundation of a reactive system that satisfies these
19+
requirements, and uses them to demonstrate the implementation of the reactive abstractions.
20+
21+
- [`tags.ts`](./pseudocode/tags.ts): A simple implementation of the tag-based validation system,
22+
including an interface for a runtime that supports tag consumptions and tracking frames.
23+
- [`primitives.ts`](./pseudocode/primitives.ts): Implementation of:
24+
- `Snapshot`, which captures a value at a specific revision with its tag validator.
25+
- `PrimitiveCell` and `PrimitiveCache`, which implement a primitive root storage and a primitive
26+
cached computation, both of which support law-abiding snapshots.
27+
- [`composition.ts`](./pseudocode/composition.ts): Implementations of the higher-level reactive
28+
constructs described in [Reactive Abstractions](./reactive-abstractions.md) in terms of the
29+
reactive primitives.
30+
31+
> [!TIP]
32+
>
33+
> While these are significantly simplified versions of the production primitives that ship with
34+
> Ember and Glimmer, they serve as clear illustrations of how to implement reactive abstractions
35+
> that satisfy the reactive laws.

guides/reactivity/laws.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# The Fundamental Laws of Reactivity
2+
3+
## ♾ The Fundamental Axiom of Reactivity
4+
5+
> ### "A reactive abstraction must provide both the current value and a means to detect invalidation without recomputation."
6+
7+
From the perspective of a Glimmer user, this axiom enables writing reactive code using standard
8+
JavaScript functions and getters that automatically reflect the current state of UI inputs.
9+
10+
**Glimmer users write UI code as straightforward rendering functions**, yet the system behaves _as
11+
if_ these functions re-execute completely whenever any reactive value changes.
12+
13+
> [!IMPORTANT]
14+
>
15+
> When root state is mutated, all reactive abstractions reflect those changes immediately, even when
16+
> implemented with caching. Glimmer's reactive values are _always coherent_ — changes are never
17+
> batched in ways that would allow inconsistencies between computed values and their underlying root
18+
> state.
19+
20+
## Definitions
21+
22+
- **Root Reactive State**: An atomic reactive value that can be updated directly. It is represented
23+
by a single [value tag](./concepts.md#value-tag). You can create a single piece of root state
24+
explicitly using the `cell` API, but containers from `tracked-builtins` and the storage created by
25+
the `@tracked` decorator are also root reactive state.
26+
- **Formula**: A reactive computation that depends on a number of reactive values. A formula's
27+
revision is the most recent revision of any of the members used during the last computation (as a
28+
[combined tag](./concepts.md#combined-tag)). A
29+
formula will _always_ recompute its output if the revision of any of its members is advanced.
30+
- **Snapshot**: A _snapshot_ of a reactive abstraction is its _current value_ at a specific
31+
revision. The snapshot <a id="invalidate"></a> _invalidates_ when the abstraction's tag has a more
32+
recent revision. _A reactive abstraction is said to _invalidate_ when any previous snapshots would
33+
become invalid._
34+
35+
## The Fundamental Laws of Reactivity
36+
37+
In order to satisfy the _Fundamental Axiom of Reactivity_, all reactive abstractions must adhere to these six laws:
38+
39+
1. **Dependency Tracking**: A reactive abstraction **must** [invalidate](#invalidate) when any
40+
reactive values used in its _last computation_ have changed. _The revision of the tag associated
41+
with the reactive abstraction <u>must</u> advance to match the revision of its most recently
42+
updated member._
43+
44+
2. **Value Coherence**: A reactive abstraction **must never** return a cached _value_ from a
45+
revision older than its current revision. _After a root state update, any dependent reactive
46+
abstractions must recompute their value when next snapshotted._
47+
48+
3. **Transactional Consistency**: During a single rendering transaction, a reactive abstraction
49+
**must** return the same value and revision for all snapshots taken within that transaction.
50+
51+
4. **Snapshot Immutability**: The act of snapshotting a reactive abstraction **must not**
52+
advance the reactive timeline. _Recursive snapshotting (akin to functional composition) naturally
53+
involves tag consumption, yet remains consistent with this requirement as immutability applies
54+
recursively to each snapshot operation._
55+
56+
5. **Defined Granularity**: A reactive abstraction **must** define a contract specifying its
57+
_invalidation granularity_, and **must not** invalidate more frequently than this contract
58+
permits. When a reactive abstraction allows value mutations, it **must** specify its equivalence
59+
comparison method. When a new value is equivalent to the previous value, the abstraction **must
60+
not** invalidate.
61+
62+
All reactive abstractions—including built-in mechanisms like `@tracked` and `createCache`, existing
63+
libraries such as `tracked-toolbox` and `tracked-builtins`, and new primitives like `cell`—must
64+
satisfy these six laws to maintain the Fundamental Axiom of Reactivity when these abstractions are
65+
composed together.
66+
67+
> [!TIP]
68+
>
69+
> In practice, the effectiveness of reactive composition is bounded by the **Defined Granularity** and **Specified Equivalence** of the underlying abstractions.
70+
>
71+
> For instance, if a [`cell`](#cell) implementation defines granularity at the level of JSON serialization equality, then all higher-level abstractions built upon it will inherit this same granularity constraint.
72+
>
73+
> The laws do not mandate comparing every value in every _computation_, nor do they require a
74+
> uniform approach to equivalence based solely on reference equality. Each abstraction defines its
75+
> own appropriate granularity and equivalence parameters.
76+
>
77+
> For developers building reactive abstractions, carefully selecting granularity and equivalence
78+
> specifications that align with user mental models is crucial—users will experience the system
79+
> through these decisions, expecting UI updates that accurately reflect meaningful changes in their
80+
> application state.
81+
>

0 commit comments

Comments
 (0)