#From Vide

The closest neighbour on this list. Both are small, both give you a callable that reads with no arguments and writes with one, and neither builds a virtual tree.

The difference is when things happen. Vide is reactive: the tree exists from mount, and a write re-runs the effects that read that source. Vision stages: Capture builds nothing, writes before Mount only store, and the callback runs once with the last value.

The comparison page measures both side by side.

#The table

Vide Vision
create "Frame" { ... } { ClassName = "Frame", ... }
the array part of a create call the array part of the declaration
source(0) event("Name", 0, Callback)
Src() Interface.Name()
Src(Value) Interface.Name(Value)
derive(function() return Src() * 2 end) derive("Name", Compute)
effect(fn) the callback you give event or merge
a second effect on one source merge("Name", Callback)
mount(Component, Target) Scope:Capture(Decl) with mount(Target), then :Mount()
the destroy function mount returns Interface:Cleanup() or Scope:Release()
cleanup(fn) cleanup(function(self, vision) end)
spring(Src, Period, Damping) Scope:PhysicsEvent(Period, Damping, Interface, { Name = Goal })
changed("Text", fn) PropertyChanged = { Text = fn }
action(fn) ready(function(self) end)
untrack not needed, dependencies are named rather than ambient
batch not needed, writes are not deferred
root(fn) Vision.Scope()
indexes, values no equivalent, build the rows yourself
show, switch no equivalent, bind Visible or mount whole visions
context no equivalent

#A counter

Vide:

local function Counter()
    local Count = source(0)

    return create "TextButton" {
        Text = function()
            return `Clicks: {Count()}`
        end,

        Activated = function()
            Count(Count() + 1)
        end,
    }
end

local Destroy = mount(Counter, PlayerGui)

Vision:

local Counter = Scope:Capture({
    ClassName = "TextButton",

    mount(PlayerGui),

    event("Count", 0, function(self, _, Value)
        self.Text = `Clicks: {Value}`
    end),

    Activated = function(_, Panel)
        Panel.Count(Panel.Count() + 1)
    end,
})

Counter:Mount()

Vide binds a property to a function and re-runs it. Vision binds a callback to a name and lets it assign whatever it likes, including several properties across several instances.

#Derives are the biggest change

Vide tracks dependencies by watching which sources you read. Vision watches nothing. You say what you read, and you can also say what happens when someone writes to the derived value:

derive("Doubled", function(Mode, Read, Write, Value)
    if Mode == "set" then
        return Write("Charge", Value / 2)
    end

    return Read("Charge") * 2
end)

Interface.Doubled() computes from Charge. Interface.Doubled(1.6) runs the "set" branch and pushes 0.8 back into Charge. Vide's derives are read only.

#Things that will trip you

Teardown destroys. Vide's destructor tears down the reactive graph and leaves the instances alone. Scope:Release() destroys the instances it created. If you were relying on instances outliving the destructor, use fromInstance, which adopts an instance and never destroys it.

Properties are not functions. Vide accepts a function for any property and re-runs it. Vision takes a plain value for a property, and reactivity comes from a named value plus a callback.

A spring lives on the scope, not on the source. Vide wraps a source in spring. Vision animates toward a goal from the scope, and the target can be a value, an instance, a style field or a plain function. See scope.

Nothing is ambient. There is no tracking context, so no untrack, and reading a value inside a callback never subscribes you to it.