Skip to content

@read and @write

ts
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

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

js
// 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:

WrittenSchedules
@on(...) above @writethe body of the handler
@write above @on(...)direct calls only
ts
@
component
({
name
: 'Demo' })
class
Demo
extends
Base
{
@
on
('click')
@
write
paint
() {}
}

Write @read and @write closest to the method body.

The function form

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

MIT Licensed