From 3831ecdf5e95b644b68bfe11ebd1aacf47195798 Mon Sep 17 00:00:00 2001 From: Michael Beckemeyer Date: Thu, 11 Apr 2024 10:52:11 +0200 Subject: [PATCH] Continue working on README --- packages/reactivity-core/README.md | 133 ++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 2 deletions(-) diff --git a/packages/reactivity-core/README.md b/packages/reactivity-core/README.md index d6b1bed..0585293 100644 --- a/packages/reactivity-core/README.md +++ b/packages/reactivity-core/README.md @@ -101,6 +101,7 @@ Computed signals can be watched (e.g. via `effect()`) like any other signal: ```ts import { reactive, computed, effect } from "@conterra/reactivity-core"; + const age = reactive(21); const doubleAge = computed(() => age.value * 2); @@ -113,8 +114,134 @@ effect(() => { age.value = 22; ``` -Note that the callback function used by a computed signal should be stateless: -it is supposed to compute a value (possibly very often), and it should not change the state of the system while doing so. +Computed signals only re-compute their value (by invoking the callback function) when any of their dependencies have changed. +For as long as nothing has changed, the current value will be cached. +This can make even complex computed signals very efficient. + +Note that the callback function for a computed signal should be stateless: +it is supposed to compute a value (possibly very often), and it should not change the state of the application while doing so. + +### Using signals for reactive object properties + +You can use signals in your classes (or single objects) to implement reactive objects. +For example: + +```ts +import { reactive } from "@conterra/reactivity-core"; + +class Person { + // Could be private or public + _name = reactive(""); + + get name() { + return this._name.value; + } + + set name(value) { + // Reactive write -> watches that used the `name` getter are notified. + // We could use this setter (which could also be a normal method) to enforce preconditions. + this._name.value = value; + } +} +``` + +Instances of person are now reactive, since their state is actually stored in signals: + +```ts +import { effect } from "@conterra/reactivity-core"; + +// Person from previous example +const p = new Person(); +p.name = "E. Example"; + +// Prints "E. Example" +effect(() => { + console.log(p.name); +}); + +// Triggers effect again; prints "T. Test" +p.name = "T. Test"; +``` + +You can also provide computed values or accessor methods in your class: + +```ts +import { reactive, computed } from "@conterra/reactivity-core"; + +// In this example, first name and last name can only be written to. +// Only the combined full name is available to users of the class. +class Person { + _firstName = reactive(""); + _lastName = reactive(""); + _fullName = computed(() => `${this._firstName.value} ${this._lastName.value}`); + + setFirstName(name) { + this._firstName.value = name; + } + + setLastName(name) { + this._lastName_.value = name; + } + + getFullName() { + return this._fullName.value; + } +} +``` + +### Effect vs. Watch + +We provide two different APIs to run code when reactive values change. +The simpler one is effect `effect()`: + +```js +import { reactive, effect } from "@conterra/reactivity-core"; + +const r1 = reactive(0); +const r2 = reactive(1); +const r3 = reactive(2); + +// Will run whenever _any_ of the given signals changed, +// even if the sum turns out to be the same. +effect(() => { + const sum = r1.value + r2.value + r3.value; + console.log(sum); +}); +``` + +If your effect callbacks become more complex, it may be difficult to control which signals are ultimately used. +This can result in your effect running too often, because you're really only interested in _some_ changes and not all of them. + +In that case, you can use `watch()`: + +```js +import { reactive, watch } from "@conterra/reactivity-core"; + +const r1 = reactive(0); +const r2 = reactive(1); +const r3 = reactive(2); + +watch( + // (1) + () => { + const sum = r1.value + r2.value + r3.value; + return [sum]; + }, + // (2) + ([sum]) => { + console.log(sum); + } +); +``` + +`watch()` takes two functions: + +- **(1)**: The _selector_ function. This function's body is tracked (like in `effect()`) and all its reactive dependencies are recorded. The function must return an array of values. +- **(2)**: The _callback_ function. This function is called whenever the selector function returned different values. The callback itself is _not_ reactive. + +In this example, the callback function will only re-run when the computed sum truly changed. + +### Reactive collections ## API @@ -124,6 +251,8 @@ it is supposed to compute a value (possibly very often), and it should not chang ### Collections +### Types + ## Gotchas ### Avoid side effects in computed signals