|
| 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