Open DrSensor opened 2 years ago
NO NEED FOR DECORATOR❗
Just effect(...)
and done
export default class {
count = 0
tick = 0
constructor() {
effect(passive => console.log(
this.count, // tracked
passive.tick, // not tracked
))
}
// <span :: text:=computed />
get computed() {
return effect(passive => passive.tick + this.count)
}
/* or can be simplified as
readonly computed = effect(passive => passive.tick + this.count)
*/
// <button :: on:click="increment">++</button>
increment() { this.count++ } // print into console
// <button :: on:click="decrement">++</button>
decrement() { this.tick++ } // update <span> text
}
Note that due to automatic binding (loose mode),
effect
only run afterconstructor
even though it's called inside it. Usingeffect
as decorator might make more sense 🤔import { effect, passive } from "nusa/reactive" // yup, this module `export let passive`
export default class { count = 0 tick = 0
constructor() {}
@effect #logger() { console.log( this.count, // tracked passive.tick, // not tracked )) }
// @effect get computed() { return passive.tick + this.count }
// increment() { this.count++ } // print into console
// decrement() { this.tick++ } // update text }
> Another approach is by signaling that class scope cause reactive effect
```js
export default class {
count = 0
tick = 0
constructor() {
this.#enableLogger() // WARNING: need to be explicitly called
}
#enableLogger() {
const passive = effect() // signal that this method may cause reactive effect
console.log(
this.count, // tracked
passive.tick, // not tracked
))
}
// <span :: text:=computed />
get computed() {
const passive = effect()
return passive.tick + this.count
}
// <button :: on:click="increment">++</button>
increment() { this.count++ } // print into console
// <button :: on:click="decrement">++</button>
decrement() { this.tick++ } // update <span> text
}
🧑⚖️ Verdict: decorator WIN!
Using it on specific lifecycle 🤔
import { use } as at from "nusa/std"
import { effect, idle } from "nusa/reactive"
import * as at from "nusa/lifecycle"
import Counter from "./counter.js"
import Program from "./program.js" // let say it extends Counter
// and both modules <link> inside the same <render-scope>
at.render(() => {
const counter = use(Counter), $counter = idle(counter)
const program = use(Program), $program = idle(program)
// or maybe `$program = effect.passive(program)` 🤔
effect(() => {
console.log("Counter:"
, counter.count // tracked
, $counter.tick) // not tracked
console.log("Program:"
, $program.count // not tracked
, program.tick) // tracked
})
})
reset
default:
false
(for performance)
Reset dependency graph after effect
finish executed. When false
, the dependency graph will incrementally build up instead of being renewed each time effect
is executed.
export default class {
location = '37°46′39″N 122°24′59″W'
zipCode = "94103"
preference = "location"
// @effect({ reset: true })
@effect.reset get weather() {
switch (this.preference) {
case "location":
return lookUpWetherByGeo(this.location)
case "zip":
return lookUpWetherByZip(this.zipCode)
default:
return null
}
}
}
props/attrs bound to weather
will get updated either when:
this.preference
changethis.location
change only when the previous this.preference === "location"
this.zipCode
change only when the previous this.preference === "zip"
Making every properties and accessors reactive when initialize effect decorator is problematic because there is no way to get accessor of private field via Class.prototype
. So marking them as @active accessor
is necessary.
export default class {
@active accessor tick = 0
@active static accessor #sum = 0
@effect get blow() { // not run until it's accessed
return (this.#sum -= this.tick)
}
@effect grow(value = 0) { // not run until it's called
if (effect.invoke) { // first time invoke
this.#sum += value
this.tick // listen to tick changes
} else { // continuous effect
this.#sum += this.tick + value
}
// TODO: How to persist last `value` 🤔
}
@effect.invoke #log() { // run immediately after instantiation
console.log("tick", this.tick)
console.log("sum", this.#sum)
}
}
So yeah, @effect
should be companied with @active
for the sake of consistency, no magic. Though there is a pattern where you don't need @active accessor
be in the same class as @effect method
.
TODO:
arguments
in @effect method
Behaviour:
if (this.count++ < 5) unreact(this, "count")
)