Hey! So the other day we introduced runes which
are going to be the new way to deal with state in Svelte 5 and beyond. The reaction has been
remarkable — million plus views on the tweet, top of Hacker News, takes everywhere.
Overall, very positive reception so, you know, thank you for putting your faith in us.
Even outside the Svelte community people seemed pretty excited — every framework community was
tripping over themselves to be like hey this looks like our thing. They say that imitation is the
sincerest form of flattery but I actually think the most sincere form of flattery is accusing
someone of copying you. I guess I should set the record straight on that front — we essentially
designed this stuff from first principles based on everything that we've learned since Svelte 3 came
out in 2019, but the intellectual context in which we did so was very much influenced by the work of
Ryan Carniato on the Solid team, so everyone else, I'm afraid we weren't really thinking about you
but I'm glad you like it anyway. That said there were some naysayers and the bulk of the negative
reaction that we saw was basically "I don't want to have to write getters and setters everywhere",
or "this is way more code for basic things" Even the great Fireship who I have boundless respect
for got this totally wrong and that's our fault because we didn't do a good enough job of
explaining it. So in this short video I want to explain what getters and setters are why
we're using them and why it's not going to result in you writing a bunch more code. So first up
what are getters and setters? Well imagine that I'm creating a createCounter function in regular
JavaScript. I type createCounter up here I declare a count value, I add an increment function
and then I return count and increment directly down here and then I call the function to get a
counter instance. I log counter dot count and I get zero — great. Now I increment the counter
like that and log the count again it's still zero. Argh! What happened? Well you can see what's
happened — the count is fixed at the time we call the function, we're just returning an object
that has a count property whose value is zero, and the way that we fix that is with a get
property. So I'm just going to copy this code and this time we're going to do get count and
then I'm going to return count like that, right, and then if I do counter equals createCounter,
counter dot count — zero, obviously. counter dot increment and then counter dot count again
— we now get one which is what we want. Now we could also have a corresponding set property which
would mean that anyone who had a reference to this counter would be able to set the value of counter
dot count to whatever they wanted, but that's not what we want — in this case we only want people
to be able to increment this value. You'll notice also that we can't destructure the value — we
can't do const count increment equals counter. I mean we can but now if we call increment like
that then it's not going to do anything useful, we're always going to get the same value because
the the get method is invoked at the time of destructuring. Now if this syntax is new to you
then this might all seem a little bit weird, a little bit funky. But you actually use this
stuff every day as a web developer whether you realize it or not, because web APIs are full
of this sort of thing. So here's an example. If I log out location.href I get example.com if
I set location dot hash equals hello then, oops, syntax error, then you'll notice that the URL bar
has actually updated here because we're not just setting a property we've invoked a setter function
and then if I log location dot href again you'll see that that new hash is part of the string that
gets returned because we didn't just get access a property we again we invoked a getter function.
So this is all just JavaScript — this is just how the language works and it'd be pretty weird
honestly if it worked any differently. So what happens when we throw runes into the mix? Well
the only difference between this and what we had before is that we have this state rune in our
declaration and what this means is that Svelte now understands that wherever this value is read, for
example inside this expression right here, that thing will need to update if the count value ever
changes, for example because someone clicked this button and ran this function. That's all. That's
the only difference. And so at this point you might be thinking okay I understand that but why
getters? Why isn't it just a normal method? And it totally could be, like we could do this instead —
just a normal method and then we invoke it inside the template and it it will continue to work.
You can do that, it's your choice. The reason that we prefer getters is three-fold. First, and
maybe the least important point — it's idiomatic, right? Property access is how we read and write
values in JavaScript there's a reason that we don't have location dot getHref and location dot
setHash for example — it's just not idiomatic. Secondly is because of TypeScript and this is
a little easier to show rather than describe, so I have a little demo here. So here we have a
function that returns an object with a pair of methods get and set and the thing inside it, this
type argument, this T value could be anything. So down here I have user which is a wrapper of User
or null, like we don't know which one it is. And now down here we have some code that runs when
there is a user value when we call user dot get there is a value, means the user's logged in or
whatever, and so inside that we can do some some console dot logging, but TypeScript thinks that
this value here could be null, because that's what this function returns, because that's the value
of of the type argument. And so we get this red squiggly here and we have to fix it by, you know,
we could make a non-null assertion or we could have a temporary variable like const u equals
user dot get, and then inside there you know if if you and then the same thing inside there like
all of a sudden it works but, like, ain't nobody got time for that. Another approach that we could
do is we could use the the getter and the setter, like here. So now we have get value and set value
and down here TypeScript understands that if user dot value which here could be user or null,
inside that if block we know that it can only be a user and that's something called type narrowing
and when you have a function call between you and the value that you're trying to access TypeScript
starts to to get a little bit confused unless you're using getters and setters because it
understands that those are idempotent. Finally, in Svelte, we have bindings, right — if we
look at the our fine grained to-do example, which you can find in in the docs over here, then
we have these bindings inside the template which, if I toggle these, you can see that remaining
count changes and the reason that we can do this and we don't have to have all of this laborious
event handling code — like, I need to remember what the event is like blah blah blah blah blah —
the reason we can do that is because we have this symmetry between reads and writes. But okay, so:
the main thing people were worried about was "I don't want to write all of this shit all over my
codebase!" And you're not going to have to for two reasons. The first is that you only need that
stuff when state needs to cross a scope boundary and what I mean by that is within the scope where
a piece of state was declared you can interact with it directly, right — you only need to provide
a getter or a method or whatever when you need to read the value outside that scope, as in our
counter example. Most of the time when you're dealing with state you're doing so locally —
either it's component state, or a prop which also doesn't need this treatment, or, you know, you're
writing some complex business logic where most of the state references are internal and you just
need to expose the end result of some process. So even though with been focusing on this case in the
blog post and the video and all of that stuff in order to show how the new stuff works, it's really
the exception rather than the rule. And the second reason is that we're going to develop patterns
for dealing with this stuff, right? So let's take Fireship's example where we have a piece of
state that we want to interact with outside the scope of this module. So here's that implemented
here, right — we're exposing a a writable store as count and then inside our app we're interacting
with that store and reading the store value. So in Vue we might use something called a ref, and a ref
looks a little bit like this, all right: let value equals state initial... and then we'll return get
value return excuse my inability to type under pressure... and then the corresponding setter,
right, and then we instead of using the writable function we'll just use ref and then inside here
we'll change these to count dot value, right, and it just works exactly the same way as it did
before. So, you know, you can imagine that this is imported from a utility module or somewhere —
maybe it's even exported from Svelte itself — TBD. So you're not having to reimplement that every
time you want to have some piece of state that is shared between different components. If you prefer
Solid's approach to Vue's approach, they have a createSignal function which looks like this. And
again, we're just going to create a reactive piece of state and then this time we're going to return
a pair of functions, one for reading the value and one for writing the value, right, and then we
could do export const getCount setCount equals createSignal zero like so and then inside here,
you know, we could do getCount setCount and then this would become getCount and then inside the
event handler we want to do setCount, like this, right, and it works just the same way. So we
can implement all of these different reactivity patterns using the primitives, right? And candidly
we just don't know yet what is going to end up feeling most natural so for now we're just
giving you these primitives in a very raw form and we've already seen people produce some really
interesting abstractions on top of them for doing, you know, things like deeply nested reactive
objects. By the time Svelte 5 ships we'll hopefully have a clearer picture of this stuff,
so we'll know what we're going to include in the framework to make dealing with this stuff feel
very natural and very lightweight. Alright, so I hope that clears things up a bit. Keep
the feedback coming, it is super helpful. Bye!