Subscribe a function to run every time certain values change.
The function is run asynchronously, first after being created, then again after there are changes in any of the values or cached functions it read during its previous run.
The created subscription is tied to the currently-active job (which may be
another rule). So when that job is ended or restarted, the rule will be
terminated automatically. You can also terminate it early by calling the
"stop" function that is both passed to the rule function and returned by
rule()
.
Note: this function will throw an error if called without an active job. If you need a standalone rule, use rule.detached().
A function that can be called to terminate the rule.
Readonly
methodDecorate a method or function to behave as a rule, e.g.
const animate = rule.factory(requestAnimationFrame);
class Draggable {
@animate.method
trackPosition(handleTop: number, handleLeft: number) {
const {clientX, clientY} = lastMouseEvent();
this.element.style.top = `${clientY - handleTop}px`;
this.element.style.left = `${clientX - handleLeft}px`;
}
}
// Start running the method in an animation frame for every change to
// lastMouseEvent, until the current job ends:
someDraggable.trackPosition(top, left);
or:
const logger = rule.method((formatString, signal) => { log(formatString, signal()); });
Each time it's (explicitly) called, the decorated method will start a new
rule, which will repeatedly run the method body (with the original
arguments and this
) whenever its dependencies change, according to the
schedule defined by the rule factory. (So e.g. @rule.method
will
update on the microtask after a change, etc.)
The decorated method will always return a DisposeFn to let you
explicitly stop the rule before the current job end. But if the original
method body doesn't return a dispose function of its own, TypeScript will
consider the method to return void, unless you explicitly declare its
return type to be DisposeFn | void
.
Also note that since rule methods can accept arbitrary parameters, they
do not receive a stop
parameter, and must therefore use rule.stop() if they wish to terminate themselves.
Rest
...args: any[]Readonly
stopA function that will stop the currently-executing rule. (Accessing this attribute will throw an error if no rule is currently running.)
Note: this returns the stop for the current rule regardless of its
scheduler, so you can access rule.stop
even if the rule was created
with a different scheduler.
Create a "detached" or standalone rule, that is not attached to any job.
r.detached(fn)
is shorthand for calling detached.run(r, fn)
. (Where
r
is a RuleFactory such as rule
.)
Note that since the created rule isn't attached to a job, it must be explicitly stopped, either by calling the returned disposal function or by the rule function arranging to stop itself via rule.stop() or its stop parameter.
Return a rule factory for the given scheduling function, that you can then use to make rules that run in a specific time frame.
// `animate` will now create rules that run during animation fames
const animate = rule.factory(requestAnimationFrame);
animate(() => {
// ... do stuff in an animation frame when signals used here change
})
(In addition to being callable, the returned function is also a
RuleFactory, and thus has a .method
decorator, .if()
method,
and so on.)
A single-argument scheduling function (such as requestAnimationFrame, setImmediate, or queueMicrotask). The rule scheduler will call it from time to time with a single callback. The scheduling function should then arrange for that callback to be invoked once at some future point, when it is the desired time for all pending rules on that scheduler to run.
A RuleFactory, like rule. If called with the same scheduling function more than once, it returns the same factory.
Observe a condition and apply an action.
For a given RuleFactory r
(such as rule
), r.if(condition, action)
is roughly equivalent to r(() => { if (condition()) return action(); })
, except that the rule is only rerun if the action
's
dependencies change, or the truthiness of condition()
changes. It
will not be re-run if only the dependencies of condition()
have
changed, without affecting its truthiness.
This behavior can be important for rules that nest other rules, have cleanups, fire off tasks, etc., as it may be wasteful to constantly tear them down and set them back up if the enabling condition is a calculation with frequently-changing dependencies.
This is just a shortcut for wrapping condition
as a signal
that converts it to boolean. So if you already have a boolean signal,
you can get the same effect with just if (condition()) { ... }
.
Change the scheduler used for the currently-executing rule. Throws an error if no rule is running.
Optional
scheduleFn: SchedulerFnOptional: The scheduling function to use; the default microtask scheduler will be used if none is given or the given value is falsy.
It's best to only use scheduling functions that were created outside the current rule, otherwise you'll be creating a new scheduling queue object on every run of the rule. (These will get garbage collected with the scheduling functions, but you'll be creating more memory pressure and using more GC time if the rule runs frequently.)
The interface provided by rule, and other rule.factory() functions.