Releases: lume/classy-solid
v0.4.3
v0.4.0 - private signals
What's Changed
Details
BREAKING: There's a breaking change on how to apply @signal
to a getter/setter, see below.
- feat: Place
@signal
decorator descriptors in the same locations as decorated class members to more closely align the object shape with what a user wrote. For example if a field was decorated with@signal
, the signal descriptor will be on the instance, and if an auto or getter/setter accessor was decorated with@signal
then the descriptor will be on the prototype- BREAKING: Getters/setters decorated with
@signal
no longer result in own properties on instances, but result in a prototype property. For example, any checks likeObject.hasOwn(this, 'prop')
for a property namedprop
defined as@signal get prop() {}
will now returnfalse
instead oftrue
. Features likeObject.assign
will no longer work on those properties. Etc. If you need the properties to be own, migrate to using@signal prop
instead of a getter/setter, or update the dependent code to do something else.
- BREAKING: Getters/setters decorated with
- feat: add the ability to finalize
@signal
fields without a class decorator by defining@signal #finalize
after all fields are defined. For example instead of this,we can write this:@reactive class MyClass { @signal foo = 1 @signal bar = 2 }
The latter version has an advantage: fields are signalified by the time any code inclass MyClass { @signal foo = 1 @signal bar = 2 @signal #finalize }
constructor
runs, making it possible to rely on the signal fields insideconstructor
. For example the following will not work when using the@reactive
class decorator approach:but the following will work:@reactive class MyClass { @signal foo = 1 constructor() { createEffect(() => console.log(this.foo)) // This effect will never re-run (do not rely on signal fields in constructor in this case) } }
Just make sure thatclass MyClass { @signal foo = 1 @signal #finalize constructor() { createEffect(() => console.log(this.foo)) // This effect will re-run (signal fields are reliable in constructor) } }
@signal #finalize
comes after all class field definitions; any fields that come after@signal #finalize
will not be signalified.
It is up to you which form you prefer. If you do not need the signals withinconstructor
, you might like the aesthetic look of the@reactive
class decorator more than an unused#finalize
field, or, at an implementation level you might like the purity of the@signal #finalize
approach because it does not make a subclass like@reactive
does. - feat: refactor
@signal
on getters/setters to work without a class decorator (adding support for#private
getters/setters, but now requiring the@signal
decorator to be placed on both the getter and the setter otherwise it won't work and an error will be shown in console)- BREAKING: getter/setter pairs no longer require the use of the
@reactive
class decorator, but the@signal
decorator must now be placed on both the getter and setter of a property definition:To migrate, add an extra// Bad (this example will show a helpful error in console in case you forget to decorate *both* the getter and setter, and the property will not be reactive) class MyClass { @signal get foo() {...} set foo(value) {...} } // Good (the property will be reactive) class MyClass { @signal get foo() {...} @signal set foo(value) {...} }
@signal
to your getter/setter so that both are decorated. Optionally delete the@reactive
decorator from your class if you are using the new@signal #finalize
or no fields at all (f.e. only autoaccessor
s). - This now supports
#private
getters/setters:class MyClass { @signal get #foo() {...} @signal set #foo(value) {...} }
- BREAKING: getter/setter pairs no longer require the use of the
- feat: add support for public and
#private
auto accessors. For example, and as an alternative to@reactive
or@signal #finalize
approaches, we can now write "auto accessor" fields:It also supportsclass MyClass { @signal accessor foo = 1 constructor() { createEffect(() => console.log(this.foo)) // This effect will re-run. } }
#private
"auto accessor" fields:class MyClass { @signal accessor #foo = 1 }
The main benefit of this update is that we now have a way to apply @signal
to #private
properties, but note that only auto accessors and getters/setters are supported when it comes to making them #private
, and fields cannot not be private due to the current version of the decorator spec:
@reactive
class MyClass {
// This is impossible with the current decorator spec, an error will be thrown to let you know.
@signal #foo = 1
// In TypeScript you can at least mark the field "private" for type checking if you prefer to use fields:
@signal private foo = 1 // (not actually private at runtime)
// This works.
@signal accessor #foo = 1
// This works too.
@signal get #foo() {...}
@signal set #foo(v) {...}
}
Full Changelog: v0.3.9...v0.4.0
v0.3.8
v0.3.7
- fix: improve
Effectful.createEffect
so that nested calls work as expected just as regular Solid.jscreateEffect
calls do, otherwise they unintentionally outlive their parent effect db3a106 - feat: add
syncSignals
,createSyncedSignals
, andcreateStoppableEffect
(see README for details) db3a106
Full Changelog: v0.3.6...v0.3.7
v0.3.6
- fix: ensure that
signalify()
does not count as reading a dependency, to avoid infinite loops. Only a user's explicit read of a property after theysignalify
a property should count as a dependency ready (this was namely a problem whensignalify()
was a no-op when the property was already signalified before hand elsewhere, such as a signalified object from a library) e89bbe0
Full Changelog: v0.3.5...v0.3.6
v0.3.5
Full Changelog: v0.3.4...v0.3.5
v0.3.4
Full Changelog: v0.3.3...v0.3.4
v0.3.3 - Effectfully effectful
Features
Effectful
Effectful
is a class-factory mixin that gives your class a createEffect()
method, along with a stopEffects()
method that will stop all current effects.
Here's an example that shows a custom element that starts effects on connected,
and cleans them up on disconnect:
import {reactive, signal, Effectful} from 'classy-solid'
@reactive
class CounterDisplay extends Effectful(HTMLElement) {
@signal count
get double() {
return this.count * 2
}
constructor() {
super()
this.attachShadow({mode: 'open'})
this.shadowRoot.innerHTML = `<div></div>`
}
connectedCallback() {
// Create some effects
this.createEffect(() => {
this.shadowRoot.firstElementChild.textContent = `Count is: ${this.count}`
})
this.createEffect(() => {
console.log('count:', this.count)
})
this.createEffect(() => {
console.log('double:', this.double)
})
}
disconnectedCallback() {
// Stop the effects
this.stopEffects()
}
}
customElements.define('counter-display', CounterDisplay)
createEffect()
creates a single owner root for all effects for the current
instance, unless it is called inside another root in which case it'll use that
root.
Effects
An instantiation of Effectful(Object)
as a shortcut.
Useful when not extending from a mixin:
class MyClass extends Effects {
constructor() {
this.createEffect(() => {...})
this.createEffect(() => {...})
}
dispose() {
this.stopEffects()
}
}
const o = new MyClass()
// ...later, when finished...
o.dispose()
Useful when separate groups of effects are needed where each group can be stopped indepently of others.
class MyClass {
specialEffects = new Effects()
otherEffects = new Effects()
doSpecialStuff() {
this.specialEffects.createEffect(() => {...})
this.specialEffects.createEffect(() => {...})
}
cleanupSpecialStuff() {
this.specialEffects.stopEffects()
}
doOtherStuff() {
this.otherEffects.createEffect(() => {...})
this.otherEffects.createEffect(() => {...})
}
cleanupOtherStuff() {
this.otherEffects.stopEffects()
}
}
Full Changelog: v0.3.0...v0.3.3
v0.3.0 - Getting classier!
Fixes:
- ensure that subclass fields decorated with @signal do not lose reactivity
BREAKING:
- We updated to the latest TypeScript and removed our custom decorator parameter type definitions and are now using types from TypeScript proper.
- You may need to update your TypeScript version.
- There will now be valid type errors that can happen with certain uses of decorators, which could require code changes. F.e. TypeScript will show a type error for class decorators that return a subclass if the decorated class has a
private
constructor, etc.
Full Changelog: v0.2.3...v0.3.0
v0.2.3 - Buildless Unconstructive Reactivity
- Ensure that constructors are not reactive, i.e. that if you
new YourClass
inside of an effect, theconstructor
will beuntrack
ed and reading any properties during construction will not cause the effect to track them. It makes more sense that, when you're creating something, you can read your variables to map to further variables, without that counting as something that you want to automatically run again, otherwise you'll re-create the object unintentionally, which is probably not what you want. If you do want that, you can read properties outside of the constructor, before or after younew YourClass
to make the intent explicit. - Also commit
dist/
build outputs. This makes things easier for people, so they can consume the build outputs without needing to run a build (f.e. avoiding potential build issues in our less-tested environments such as Windows PowerShell).