This is outdated documentation for Sycamore.
For up-to-date documentation, see the latest version (v0.8).
Reactivity
Instead of relying on a Virtual DOM (VDOM), Sycamore uses fine-grained reactivity to keep the DOM and state in sync. In fact, the reactivity part of Sycamore can be used on its own without the DOM rendering part.
Signal
Reactivity is based on reactive primitives. A Signal
is data that is reactive. Here is an example:
use *;
let state = new; // create an atom with an initial value of 0
Now, to access the state, we call the .get()
method on state
like this:
println!; // prints "The state is: 0"
To update the state, we call the .set(...)
method on state
:
state.set;
println!; // should now print "The state is: 1"
Effects
Why would this be useful? It’s useful because it provides a way to easily be notified of any state changes. For example, say we wanted to print out every state change. This can easily be accomplished like so:
let state = new;
create_effect;
// prints "The state changed. New value: 0"
// (note that the effect is always executed at least 1 regardless of state changes)
state.set; // prints "The state changed. New value: 1"
state.set; // prints "The state changed. New value: 2"
state.set; // prints "The state changed. New value: 3"
How does the create_effect(...)
function know to execute the closure every time the state changes?
Calling create_effect
creates a new “reactivity scope” and calling state.get()
inside this
scope adds itself as a dependency. Now, when state.set(...)
is called, it automatically calls
all its dependents, in this case, state
as it was called inside the closure.
What’s that cloned! macro doing?
The
cloned!
macro is an utility macro for cloning the variables into the following expression. The previouscreate_effect
function call could very well have been written as:create_effect );
This is ultimately just a workaround until something happens in Rust RFC #2407.
Memos
We can also easily create a derived state using create_memo(...)
which is really just an ergonomic
wrapper around create_effect
:
let state = new;
let double = create_memo;
assert_eq!;
state.set;
assert_eq!;
create_memo(...)
automatically recomputes the derived value when any of its dependencies change.
Now that you understand sycamore
’s reactivity system, we can look at how to use this to update the
DOM.
Using reactivity with DOM updates
Reactivity is automatically built-in into the template!
macro. Say we have the following code:
use *;
let state = new;
let root = template! ;
This will expand to something approximately like:
use *;
let state = new;
let root = ;
If we call state.set(...)
somewhere else in our code, the text content will automatically be
updated!
Common pitfalls
Dependency tracking is topological, which means that reactive dependencies (like a Signal
) must be accessed (and thus recorded as reactive dependencies) before the tracking scope (like a create_effect
) returns.
For example, this code won’t work as intended:
create_effect
We’ll find that any Signal
s we track in the create_effect
won’t be tracked properly in the wasm_bindgen_futures::spawn_local
, which is often not what’s intended. This problem can be gotten around by accessing reactive dependencies as needed before going into a future, or with this simple fix:
create_effect
All we’re doing there is accessing the dependency before we move into the future, which means dependency tracking should work as intended.