-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Runes: $member
rune to reduce getters and setters boilerplate
#9289
Comments
You can check my solution here, I personally think that |
Oh, this is really cool, I didn't think about modifying a function on the fly, I might try prototyping my idea using this. Regarding the $expose rune, it's cool that it saves having to write out both getter and setter, but I'd also love to be able to define state on the fly, like: export const counter = {
count: $expose($state(0))
}; And in this case, I think putting a rune before the full object is a little more readable export const counter = $object({
count: $state(0)
}); The perfect DX I think lies somewhere in between |
$object
rune$object
rune to reduce getters and setters boilerplate
If I could directly modify what svelte is doing or add a preprocessor I can probably get Then some object might be created as follows let state1 = $stare()
let stare2 = $state()
let someOther = $state()
// only state
export const stateObj = {
someOther: $expose(someOther),
singleState: $stateMember(0),
// here we should be able to use any reactive value
dirivedSingle: $derivedMember(state1 * state2),
// these may also be allowed to be standalone runes, in which case the output might change a little
...$exposeMembers({
state1,
state2
}),
...$stateMembers({
state3: 0,
state4: 1,
}),
// here we should be able to use any reactive value
...$derivedMembers({
state1PState2: state1+state2,
state1MState2: state1-state2
})
} which then compiles to equivalent of let state1 = $stare()
let stare2 = $state()
let someOther = $state()
let singleState_randomstr = $state(0)
let state3_randomstr = $state(0)
let state4_randomstr = $state(1)
// only state
export const stateObj = {
get someOther() {
return someOther
},
set someOther(someOtherNew) {
someOther = someOtherNew
},
get singleState() {
return singleState_randomstr
},
set singleState(singleStateNew) {
return singleState_randomstr = singleStateNew
},
// here we should be able to use any reactive value
get derivedSingle() {
return state1 * state2
},
// these may also be allowed to be standalone runes, in which case the output might change a little
get state1() {
return state1
},
set state1(state1New) {
return state1 = state1New
},
get state2() {
return state2
},
set state2(state2New) {
return state2 = state2New
},
get state3() {
return state3_randomstr
},
set state3(state3New) {
return state3_randomstr = state3New
},
get state4() {
return state4_randomstr
},
set state4(state4New) {
return state4_randomstr = state4New
},
// here we should be able to use any reactive value
get state1PState2() {
return state1+state2
},
get state1MState2() {
return state1-state2
},
} How does this look? If this looks like it has potential I can try to get this to work. |
The concept looks really good actually! I'd only be worried that we'd be proposing many new runes But I do love a dedicated rune for creating state ad hoc. So maybe it would be doable to add only one rune, but a special one that is an object containing functions, rather than being just a function itself - this I would imagine could be used for intellisense down the line I was thinking
let count = $state(0)
// only state
export const stateObj = {
countReadWrite: $member.of(count),
countReadOnly: $member.get.of(count),
// this one maybe should error if count was a derived?
countWriteOnly: $member.set.of(count),
stateReadWrite: $member.state(0),
stateReadOnly: $member.get.state(0),
stateWriteOnly: $member.set.state(0),
// derived would never expose a set I don't think
derivedReadOnly: $member.derived(0)
} This kind of feels good to write. I by no means presume there isn't room for improvement though! The destructuring also looks interesting, although feels like kind of a bother to implement. Maybe it could fit in like |
That actually seem nice. I'd personally not have
Hope my points makes sense, for a better understanding look at the code bellow: let count = $state(0)
export const statObj = {
countReadWrite: $member(count),
countReadOnly: $member.get(count),
countWriteOnly: $member.set(count),
state: $member.state(0),
stateReadOnly: $member.state.get(0),
// The above is never gonna change so might as well just have it be a static value
stateWriteOnly: $member.state.set(0),
// This can never be read, setting this variable allows you to do a
// grand total of nothing through assignments to this member other than
// waste a few lines of code
// obviously you want the derived variable to always be in sync with it's dependant,
// if you are allowed to change the value that will mean it gets desynced
// for some amount of time until one of it's dependent change,
// which can be really confusing
// That's why it can and will only define a getter
derived: $member.derived(2 * count)
// the destructured one
...$members({count})
// or maybe
...$member.multiple({count})
// maybe even make it .multy
} |
Absolutely love what you've got there! Haha, I totally missed how pointless
Last thing I'm wondering about is what if you'd want to do more logic in the setter. I'm thinking either a mapping function as an optional second parameter of let count = $state(0)
export const statObj = {
// $member rune vs just a setter to compare
countStr: $member.set(count, (v) => +v),
set countStr2(v) { count = +v; },
// and fully on board with the rest
countReadWrite: $member(count),
countReadOnly: $member.get(count),
countWriteOnly: $member.set(count),
state: $member.state(0),
derived: $member.derived(2 * count),
...$members({count})
} |
$object
rune to reduce getters and setters boilerplate$member
rune to reduce getters and setters boilerplate
Related, where I proposed something analogous to Currently know of no reason why |
The problem with using |
Using let count = $state(0)
export const statObj = {
// cool shorthand
state: $state(0),
derived: $derived(2 * count),
// but we'd still need these, no?
countReadWrite: $member(count),
countReadOnly: $member.get(count),
countWriteOnly: $member.set(count),
...$members({count})
} |
also maybe we use named properties in an object approach? let count = $state(0)
export const statObj = {
countReadWrite: $member(count),
countReadOnly: $member(count, {get: count}),
countWriteOnly: $member(count, {set: v=>count=v}),
doubleReadWrite: $member(count, {get: count*2, set: v=>v/2}),
doubleReadOnly: $member(count, {get: count*2}),
doubleWriteOnly: $member(count, {set: v => v/2}),
} or something like discards for getters and setters? let count = $state(0)
export const statObj = {
countReadWrite: $member(count),
countReadOnly: $member(count, _),
countWriteOnly: $member(_, v => count = v),
doubleReadWrite: $member(count*2, v => count = v/2}),
doubleReadOnly: $member(count*2, _),
doubleWriteOnly: $member(_, v => count = v/2}),
} doesn't seem to flow... |
I think |
We could have the object for modification of behaviour in case of a $member, rather than to define only getter or only setter |
looks much better, agreed
Sorry, I don't quite understand what you mean, is this about a way around mapping functions in getters and setters? |
Yes. Something like this: {
countStr: $member(count, {
// even if you only define one of these both are defined just without a mapper function
get(count) { return count.toString() },
set(count) { return +count }
})
} |
This looks pretty good to me. For completeness Also, renaming the resulting properties could potentially be implemented as well: let localName = $state();
return {
...$members({ propName: localName })
} |
How's this? let count = $state(0)
export const statObj = {
state: $state(0),
derived: $derived(2 * count),
countReadWrite: $member(count),
countReadOnly: $member.get(count),
countWriteOnly: $member.set(count),
// optional second parameter
doubleReadWrite: $member(count, {
get: (c) => c * 2,
set: (v) => v / 2}),
doubleReadOnly: $member.get(count, (c) => c * 2),
doubleWriteOnly: $member.set(count, (v) => v / 2),
// and best not have behavior change for multiples, would get confusing fast
...$members({count})
...$members.get({readonlyCount: count})
...$members.set({writeonlyCount: count})
} |
maybe we shouldn't worry about modified edit: or the whole modify getter setter behavior is out the window, because it's probably going to break the signal reactivity? |
Should not be an issue for signals. Anything that uses the properties will trigger the state signal, you can just test this. The single {
doubleReadOnly: $member.get(count, c => c * 2),
// vs
get doubleReadOnly() { return count * 2 }
} |
Too true. Also, for getters, it's even easier to go with {
doubleReadOnly: $member.get(count * 2)
// vs
get doubleReadOnly() { return count * 2 }
// vs
doubleReadOnly: $derived(count * 2)
// im also wondering about changing $member to $field
doubleReadOnly: $field.get(count * 2)
} That does however leave me still with some desire for consistency const a = {
text: $member(text),
textReadOnly: $member.get(text),
textWriteOnly: $member.set(text),
count: $state(0),
double: derived(count * 2),
set triple(v) { count = v / 3 },
get quad() { return count * 4;},
set quad(v) { count = v / 4; },
}
// vs
const b = {
text: $member(text),
textReadOnly: $member.get(text),
textWriteOnly: $member.set(text),
count: $state(0),
double: $member.get(count * 2),
triple: $member.set(v => count = v / 3 ),
quad: $member
.get(count * 4)
.set(v => count = v / 4),
} I think I would still love to have this as an option. Though I'm not so sure about overloading |
or maybe a const count = $state(0);
const a = {
count,
count2 = count,
/// get countReadOnly(): { return count; },
/// countReadOnly: $derived(count),
countReadOnly: $get(count),
/// set countWriteOnly(v): { count = v; },
countWriteOnly: $set(count),
double: $get(count * 2).set(v => text = v),
/// get doubleReadOnly(): { return count * 2; },
doubleReadOnly: $get(count * 2),
/// set doubleWriteOnly(v): { count = v / 2; },
doubleWriteOnly: $set(v => count = v / 2),
} still the same overload problem though edit: and dropping |
I don't think |
ah yes, that's absolutely correct. and truth is something like this then maybe: const count = $state(0);
const a = {
count: $member(count),
countReadOnly: $get(count),
countWriteOnly: $set(count),
double: $member(count * 2, v => text = v),
doubleReadOnly: $get(count * 2),
doubleWriteOnly: $set(v => count = v / 2),
} any ideas on avoiding |
I'm thinking maybe just functions for all setters? Not much win with respect to amount of code, but flows better than native setters and stays consistent const count = $state(0);
const a = {
count: $member(count),
countReadOnly: $get(count),
countWriteOnly: $set(v => count = v),
double: $member(count * 2, v => count = v / 2),
doubleReadOnly: $get(count * 2),
doubleWriteOnly: $set(v => count = v / 2),
} |
Speaking of typing, both const a = {
countReadOnly: $get(count),
}; and const a = {
...$members.get({countReadOnly: count})
}; suffer from the fact that |
Describe the problem
I love how runes can massively reduce the global state complexity.
I am also fully on board with using getters and setters so as to simplyfy the usage of the code we write.
However, because I expect we'll be writing a lot of getters and setters, I feel there should be a way, in true svelte fashion, to compile away this all too common boilerplate.
Describe the proposed solution
While playing with the syntax in the preview playground, I came across this idea:
We could write this - note the
$object()
call:And before the compiler transforms the
$state
rune into signals, the$object
rune could get lowered, filling in the getters and the setters.This would still allow for extending the objects with
$derived
signals and, still, getters and setterssuch that this:
becomes this:
I don't know too much about the under the hood of compiling runes, so I'm not sure about how hard the implementation would be, but because it's mainly code lowering, seems ok-ish?
I'd love have someone smarter disilusion me of this idea!
Alternatives considered
c# like
private int a { get; set; }
auto property syntax - not possible with standard jsplain js functions for creating compound objects - still seems boilerplatey and complicated to use
Importance
nice to have
The text was updated successfully, but these errors were encountered: