[svelte store][01] Learn about svelte's reactivity
this article describes the basic usage of svelte store
.
1. Assignments
in svlete, variables declared inside a component automatically provide reactivity.
App.svelte<script> <script>
let weather = {
temp: {
value: 26,
unit: 'C'
}
}
const changeTemperature = (value, unit) => {
weather.temp.value = value
weather.temp.unit = unit || 'C'
}
</script>
- related article - 2. Assignments are 'reactive'
when configuring the screen as shown below, pressing the button will execute the function changeTemperature
and update the temperature from "26C" to "45F".
App.svelte<button on:click={() => changeTemperature(45, 'F')}>change</button>
<h3>{weather.temp.value}{weather.temp.unit}</h3> <!-- "26C" => "45F" -->
the key to this kind of responsiveness is "using assignment".
the code below all works the same.
TS// OK
weather.temp = {value: 45, unit: 'F'}
// OK
weather = {
temp: {value: 45, unit: 'F'}
}
- assingment(
=
) is recognized as a change in the state of the leftmost variableweather
.
however, if you put a value like this, the screen does not update.
1.1. Update via child property, not reactive
when looking at weather
on the screen, updating via the property of the instance referenced by weather
does not work reactively.
App.svelte#01
const changeTemperature = (value, unit) => {
// not reactive
const { temp } = weather
temp.value = 45
temp.unit = 'F'
console.log(weather.temp) // prints {value 45, unit: 'F'}
}
- recognized as a change to the leftmost variable
temp
- you can see the state change through the variable, but the
- screen does not change automatically.
because we write new weather data through the variable temp
, we don't recognize it as a change to the weather
we see on the screen.
similarly, App.svelte#02
changes its data, but the screen doesn't update.
App.svelte#02
const { temp } = weather // not reactive
const changeTemperature = (value, unit) => {
temp.value = 45
temp.unit = 'F'
console.log(weather.temp) // prints {value 45, unit: 'F'}
}
- recognized as a change to variable
temp
if you modify the view to look at temp
as follows, App.svelte#02
will work correctly.
App.svelte#02<h3>{temp.value}{temp.unit}</h3> // updated reactively
because we're using assignment as a rule for reactivity, we can always update the screen reactively by using something like this anyway.
TS
const changeTemperature = (value, unit) => {
// do some updates...
weather = weather; // re-assign
}
- it's not very elegant, but it works.
- the code above is a trick of sorts, but not a recommended practice.
1.2. Update via method, not reactive
similarly, when changing state via method inside a svelte file, the screen is not updated reactively.
App.svelte<script>
import { writable } from 'svelte/store';
let weather = {
temp: {
value: 26,
unit: 'C'
},
changeTemp(value, unit) {
this.temp.value = value;
this.temp.unit = unit || 'C';
}
}
const changeTemperature = (value, unit) => {
weather.changeTemp(value, unit);
}
</script>
- the state of
weather
has changed. but the screen is not updated.
2. Sharing through props
these rules are the same when sharing the state of a parent component to child components.
Important differences are discussed below.
share the state to the child component WeatherView
as shown below.
App.svelte<script>
import WeatherView from './Weather.svelte';
let weather = {
temp: {
value: 26,
unit: 'C'
}
}
</script>
<WeatherView {weather}/>
{weather}
- a shorthand syntax forweather={weather}
the child component WeatherView.svelte
references the weather shared by the parent, as shown below.
WeatherView.svelte<script
export let weather;
</script>
<span>Current: {weather.temp.value}{weather.temp.unit}</span>
- when referencing state shared by a parent component, declare it with
export let variable;
.
when you change the weather
in the parent component App.svelte
, all of the child components (and their children) will reactively update their screens.
2.1. One-way binding
however, if a child component modifies the state shared by the parent component, the parent component will not reflect this change on the screen.
try adding functionality to the child component as follows.
WeatherView.svelte<script> <script>
export let weather;
const increase = () => {
// updates state of parent component
weather.temp.value++;
}
</script>
<span>Current: {weather.temp.value}{weather.temp.unit}</span>
<button on:click={increase}>Change from child</button>
you have incremented the temperature in the child component through the shared variable weather
.
this state change is also shared in the variable weather
referenced by the parent component App.svelte
.
however, the screen in App.svelte
is not automatically updated.
this is called a one-way binding in svelte.
2.2. Bidirectional binding
there is a way to bypass the rules between parent and child components.
apply the bidirectional binding keyword in the parent component App.svelte
as follows.
App.svelte<script>
...
</script>
<WeatherView bind:weather={weather}/>
- bind:prop={value}` - prop matches the variable name declared in the child component. and the value is the variable in the current component.
with this syntax, the weather variable weather
is treated as if it were a global variable.
whether you change it in the parent or the child component, they both share this state change and the screen is updated.
3. the need for a svelte store
while the screen update rule via assignment seems convenient, there are a lot of inconveniences with it.
3.1. Breaks ownership of state
as in the example in [2.1. One-Way Bindings], allowing a child to arbitrarily change state shared by a parent breaks the ownership of the state, making it harder to trace the change (meaning harder to debug)
to compensate for this drawback, the svelte store provides a readable
function.
SVELTEimport { readable, writable } from 'svelte/store';
- this is typically used to wrap state in
writable
and share state as read-only to other components.
3.2. Programming Constraints
if the screen can only be changed via assignment, you'll have to do the following trick for every state change.
Svelteconst changeTemperature = (value, unit) => {
weather = weather; // re-assignment
}
- when updating an element of an array
- when changing the state via sub property
- when changing the state by calling a method
to keep your view as simple as possible, you should isolate the functions that manage state in a separate class, and the view should be updated reactively through method calls and state retrieval.
The less logic in the view, the better.
reactivity via assignment is a useful feature, but it's not enough to keep the code lean.
we use the features provided by the svelte store to implement models and views concisely.