Svelte 5 runes: what's the deal with getters and setters?

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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!
Info
Channel: Rich Harris
Views: 39,770
Rating: undefined out of 5
Keywords:
Id: NR8L5m73dtE
Channel Id: undefined
Length: 11min 22sec (682 seconds)
Published: Sat Sep 23 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.