I've been doing front-end interviews this summer, which usually involve moving variables between React components. It's something I'm terrible at.
For years, jQuery applications were denounced for causing 'spaghetti code' - where variables were scattered throughout different callbacks. It made reasoning about the application tricky.
React was welcomed and celebrated for making state/life-cycle explicit. It's really been helpful. Moving variables between components though, feels like it was always awkward, and seems to get more convoluted every year.
Andrew Clark@acdliteIf I had a time machine, I’d go back to 2016, grab my younger self by the shoulders and scream “YES GLOBAL MUTABLE STATE IS BAD BUT LOCAL MUTATION IS FINE AND YOUR INCREASINGLY CONVOLUTED EFFORTS TO AVOID IT ARE NOT MERELY MISGUIDED BUT ALSO DRIVING YOUR COLLABORATORS TO MADNESS”07:01 AM - 23 May 2019
This is what drew me to Svelte - you can change a variable from-a-distance in a spooky way. It seems to get around the need for all the spaghetti:
<!-- Parent.svelte -->
<script>
import Child from './child.svelte'
let str = 'init'
setTimeout(() => {
str = 'changed'
}, 2500)
</script>
<div>
<Child {str} />
</div>
<!-- Child.svelte -->
<script>
export let str = ''
</script>
<div>
child: <b>{$str}</b>
</div>
Here, the child's HTML updates automatically. It's actually quite a remarkable thing. It feels like half the code is missing.
There's no callbacks ...?
I'll admit, I do sometimes get stuck on control-flow issues in Svelte. There can be some puzzlers. I sometimes find myself throwing-around callbacks when I shouldn't.
Consider this example:
<script>
let value = 'foo'
</script>
<div>
<input bind:value />
magical variable: <b>{value}</b>
</div>
This variable is 'hot' - it's wired-up both-ways. If you look at the source, there's getElementById's
and addEventListener's
- the stuff you've normally been writing yourself.
There's still no callbacks?
When a parent passes a variable to a child, the child will automatically listen for changes. The parent will only listen to the child if you use bind
:
<!-- one-way (down) -->
<Child str={str} />
<!-- both-ways -->
<Child bind:str />
A grandchild can change a variable using nested binds -
prop ➔ bind:prop ➔ bind:prop
A sibling can change a variable using two binds -
prop ➔ [bind:prop, bind:prop]
In both cases, things are updated between all the components automatically, as though it were a spreadsheet.
Ok - this bind
thing is crazy. It's automating a big-chunk of web-development. It's automating the chunk that sucks. I'm feeling it.
Arguably, bind also resolves a lot of those Redux job-interview questions that I mess up.
But what about callbacks though?
I'm still a bit confused about this, admittedly.
Svelte has a way that you can declare a variable with $: instead of let - and it enters the spooky dependency-graph - where it knows when variables change somehow.
I don't fully get it, but it seems really powerful:
<!-- Parent.svelte -->
<script>
import Child from './Child.svelte'
let str = 'init'
// (fancy-part!)
$: strLen = () => {
return str.length
}
</script>
<div>
<Child bind:str />
{strLen()}
</div>
<!-- Child.svelte -->
<script>
export let str = ''
setTimeout(() => {
str = 'child-changed'
}, 2000)
</script>
The function will re-fire after the child component changes the variable - and then the DOM will magically update, too
I mean, that's a callback. I just don't have to pass it around anymore. Using this $: thing, you can listen for changes, whenever you want to - from the top, the bottom, or in a side component. No sweat.
Maybe there are callbacks
These three aspects -
- the 'hot' props (going down)
- the 'bind' concept (going up)
- the $: thing (anywhere)
They seem to be a complete interface. Maybe this is the interface we want to have for public libraries, and open-source components. I can't tell.
There seems to be no gospel about what a Svelte component interface ought to look like. A lot of libraries accept props, and callbacks for each prop that changes. This 'events-up' callback style is cool with me too, I guess.
Oh - there is also the Store concept. This is a observable sub/pup thing. I used stores a lot until I figured-out I didn't need them as much. They are more explicit and traditional about the variable updating.
Maybe Svelte components should accept writable stores as props? That would look something like this:
<!-- Parent.svelte -->
<script>
import { writable } from 'svelte/store'
import Child from './Child.svelte'
let str = writable('init')
str.subscribe(wowChanged => {
// is this a callback?
})
</script>
<div>
<Child {str} />
</div>
<!-- Child.svelte -->
<script>
export let str
setTimeout(() => {
$str = 'child-changed' //change store
}, 2000)
</script>
I guess that's cool too.
Oh, and there's a Context concept too, which avoids having to pass props down all-together. That seems cool.
Oh, and there's a ton of people adding redux back to svelte (for some reason). That seems cool.
The svelte developers are also building this galaxy-brain Multi-Page-App thing called Sapper which routes data around components presumably, too. Cool.
It's all cool. Svelte is a really fun, and flexible library to use. I hope this message-passing stuff becomes clearer, as more people jump in. I'd like to not have to think about it anymore.
Top comments (0)