@read and @write
read: (value: VoidMethod, context: ClassMethodDecoratorContext) => VoidMethod;
write: (value: VoidMethod, context: ClassMethodDecoratorContext) => VoidMethod;Runs the method body in the frame's read or write phase, cancelled on unmount. They are sugar over $read() and $write().
Usage
import { Base, component, read, write } from '@studiometa/js-toolkit';
@component({ name: 'Measure' })
class Measure extends Base {
#height = 0;
@read
measure() {
this.#height = this.$el.scrollHeight;
}
@write
apply() {
this.$el.style.setProperty('--height', `${this.#height}px`);
}
}Every read runs before every write, once, before paint. A read scheduled from a write runs in the next frame; a write scheduled from a read runs in the same frame.
They are leaf-method sugar: the phase belongs to the call site
Decorate a method nobody overrides
A phase decorator returns a wrapper around the method it decorates, and that wrapper is a property of that class. A subclass that overrides the method defines its own, undecorated, and this.method() resolves to it — so the base's scheduling disappears and the body runs in whatever phase the caller was in.
A template method — a base that schedules work its subclasses implement — schedules at the call site instead:
// in the base, where the call is
state.subscribe((value) => this.$write(() => this.update(value)));The alternative, dispatching a decorated method through something a subclass cannot replace, was refused: it would make a decorator's behaviour depend on inheritance depth, which nothing else in v4 does, to buy a convenience no consumer has asked for.
Stacking with @on
The skip is keyed by the method name, so the two stack in either order — and the order decides what the listener calls:
| Written | Schedules |
|---|---|
@on(...) above @write | the body of the handler |
@write above @on(...) | direct calls only |
@component({ name: 'Demo' })
class Demo extends Base {
@on('click')
@write
paint() {}
}Write @read and @write closest to the method body.
The function form
import { Base } from '@studiometa/js-toolkit';
class Measure extends Base {
static config = { name: 'Measure' };
#height = 0;
measure() {
return this.$read(() => {
this.#height = this.$el.scrollHeight;
});
}
apply() {
return this.$write(() => {
this.$el.style.setProperty('--height', `${this.#height}px`);
});
}
}The function form also gives you the ScheduledTask handle — its promise and its cancel() — which the decorator discards.