Sources
A Var<'T> is a value you read and write through .Value:
open Fable.Ripple
let count = Var.create 0
count.Value <- count.Value + 1
printfn "%d" count.Value // 1
Reading .Value inside a computed or an effect registers a dependency. Writing it notifies everything that depends on it.
Equal writes are no-ops
A write is compared to the current value with structural equality. Writing an equal value notifies nobody:
open Fable.Ripple
let name = Var.create "ada"
// Runs immediately: name = ada
Signal.effect (fun () -> printfn "name = %s" name.Value) |> ignore
name.Value <- "ada" // equal - nothing runs
name.Value <- "grace" // name = grace
Only grace reprints. The first write changed nothing, so nothing ran.
Watch it on the graph - writing the same value again moves nothing, writing a new one sends a dot and bumps the effect's run counter:
Custom equality
Var.createWith takes the comparison as a parameter:
open Fable.Ripple
let name =
Var.createWith
(fun a b -> System.String.Equals(a, b, System.StringComparison.OrdinalIgnoreCase))
"ada"
// Runs immediately: name = ada
Signal.effect (fun () -> printfn "name = %s" name.Value) |> ignore
name.Value <- "ADA" // equal under this comparison - nothing runs
name.Value <- "grace" // name = grace
ADA is equal under this comparison, so it does not reprint - and does not overwrite.
The read-only view
myVar.Signal is a Signal<'T>: the same value, with a getter and no setter. Hand it to anything that should read but never write:
let theme = Var.create "light"
let watch (s: Signal<string>) =
// s.Value <- "dark" would not compile
Signal.subscribe (printfn "theme = %s") s
watch theme.Signal
The wrapper costs nothing: Fable erases it, so at runtime the read-only view is the node itself. The one trace it leaves is reflection - typeof<Signal<float>> reports Var`1.
Sharing state
State lives where the let lives. A Var bound at module level belongs to the module, and every file that opens it reads the same value:
module Theme =
let private state = Var.create "light"
/// Read-only for consumers; the module owns the writes.
let current: Signal<string> = state.Signal
let set (name: string) = state.Value <- name
Module-level state never resets. That suits a theme, and not a counter that should start at zero each time its component appears. Keep per-visit state inside the component function.
Reading without subscribing
myVar.Peek() reads the current value without registering a dependency. See Batching and untracked.