Dependency-tracking reactivity

Yes indeed, I think we need the ability to cleanup when desired like in S.js or Solid.js, but also it should implicitly cleanup (be garbage collected) if all dependencies are no longer referenced (also as with those libraries).

As for explicit cleanup, how could that look like?

const effect = effect {
  // re-run when a, b, or c change
  console.log(@a, @b, @c)
}

// later:
effect.stop()

Maybe effect here is some sort of instance based on an Effect class that represents the effect. Similar to how a Function class represents instances of function() {} or Array represents [].

Also effects should be synchronously dependent (children) of the effect they're created in, and would therefore also be cleaned up with their parent:

const effect = effect {
  // re-run when a, b, or c change
  console.log(@a, @b, @c)

  const d@ = 0

  const interval = setInterval(() => @d++, 1000) // increment every second

  // inner effect (no need to keep a reference in this example)
  effect {
    // additionally log any time d changes
    console.log(@d)
  }

  // a "cleanup" block re-run any time the effect it is defined in re-runs (cleanup in S.js, onCleanup in Solid.js)
  cleanup {
    clearInterval(interval)
  }
}

// later:
effect.stop() // also cleans up the inner effect (and whichever interval is currently running)

This is very similar to S.js and Solid.js, just in a syntactical form.

I think having this be a native feature would be great because:

  • it is powerful for data modeling
  • issues with debugging are eliminated: the JS engine can provide accurate call stack when an error is thrown, even on infinite recursion (it could throw if recursion is too big just like with functions), whereas with libs like S.js and Solid.js they have to catch errors and re-throw which can get problematic (the trace you see may not match with your actual source).
  • With an official syntax, we can provide IDE tooling to help with best practices, f.e. we can syntactically highlight effects that are children or not children in differing ways, etc.
  • Declarative toolchains (f.e. JSX compilers) can have a standard output for reactivity
  • signals and effects can be implemented and optimized natively in the JS engine in different ways, while providing the top-level syntactical pattern
  • ...