UNPKG

classy-solid

Version:

Solid.js reactivity patterns for classes, and class components.

87 lines (80 loc) 2.71 kB
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 }