classy-solid
Version:
Solid.js reactivity patterns for classes, and class components.
87 lines (80 loc) • 2.71 kB
text/typescript
import type {AnyConstructor} from 'lowclass/dist/Constructor.js'
import {getListener, untrack} from 'solid-js'
/**
* A decorator that makes a class's contructor untracked.
*
* Sometimes, not typically, you may want to ensure that when a class is
* instantiated, any signal reads that happen during the constructor do not
* track those reads.
*
* Normally you do not need to read signals during construction, but if you do,
* you should use `@untracked` to avoid accidentally creating dependencies on
* those signals for any effects that instantiate the class (therefore avoiding
* infinite loops).
*
* Example:
*
* ```ts
* import {untracked, signal} from "classy-solid";
* import {createEffect} from "solid-js";
*
* @untracked
* class Example {
* @signal count = 0;
*
* constructor() {
* this.count = this.count + 1; // does not track .count signal read in any outer effect.
* }
* }
*
* createEffect(() => {
* // This does not track .count, so this effect will not re-run when .count changes.
* // If this did track .count, an infinite loop would happen.
* const example = new Example();
*
* createEffect(() => {
* // This inner effect tracks .count, so it will re-run (independent of the
* // outer effect) when .count changes.
* console.log(example.count);
* });
* });
* ```
*
* This can also be called manually without decorators:
*
* ```ts
* import {untracked} from "classy-solid";
*
* const Example = untracked(
* class {
* count = 0;
*
* constructor() {
* this.count = this.count + 1; // does not track .count signal read in any outer effect.
* }
* }
* )
*
* // ...same usage as above...
* ```
*/
export function untracked(value: AnyConstructor, context: ClassDecoratorContext | undefined): any {
// context may be undefined when unsing untracked() without decorators
if (typeof value !== 'function' || (context && context.kind !== 'class'))
throw new TypeError('The @untracked decorator is only for use on classes.')
const Class = value
class ReactiveDecorator extends Class {
constructor(...args: any[]) {
let instance!: ReactiveDecorator
// Ensure that if we're in an effect that `new`ing a class does not
// track signal reads, otherwise we'll get into an infinite loop. If
// someone want to trigger an effect based on properties of the
// `new`ed instance, they can explicitly read the properties
// themselves in the effect, making their intent clear.
if (getListener()) untrack(() => (instance = Reflect.construct(Class, args, new.target))) // super()
else super(...args), (instance = this)
return instance
}
}
return ReactiveDecorator
}