Start typing to search packages!
signals
By @grilme99
Roblox
Mirroredsignals
[image: Get it on Creator Store]
[image: Contributions welcome]
[image: Wally (external link)]
Scalable and minimal reactive programming framework for Luau.
Overview
Signals provides fine-grained reactivity through a minimal set of primitives that automatically track dependencies and efficiently propagate updates. It enables building reactive systems where only the necessary computations re-run when data changes.
Motivation
Those with limited experience in reactive programming may find this article helpful as a pre-read.
The initial motivation for this library originated with the desire for a performant state management solution. In particular, we sought to move from a global "Redux-like" state towards a distributed "fine-grained" state graph.
Usage
The core API is very minimal, consisting of three core primitives:
createSignal
createSignal<T>(initial: (() -> T) | T, equals: equals<T>?): (getter<T>, setter<T>)
Creates a queryable and settable value.
- Lazy-initializable with a constructor
- Cacheable with an optional
equalsparameter - The getter can be provided a
scopefor automatic dependency tracking (see createComputed and createEffect)
local getFirstName, setFirstName = createSignal("David")
local getLastName, setLastName = createSignal("Tennant")
print(getFirstName(false)) -- prints: David
setFirstName("The")
setLastName("Doctor")
print(`{getFirstName(false)} {getLastName(false)}`) -- prints: The Doctor
createComputed
createComputed<T>(computed: (scope) -> T, equals: equals<T>?): getter<T>
Creates a read-only reactive derived value.
- Lazy evaluation (computed updates when value is read)
- Can be used to define "derived" state using signals and other computeds
- The
scopecan be used to automatically and reactively track updates to dependencies
local getFullName = createComputed(function(scope)
return `{getFirstName(scope)} {getLastName(scope)}`
end)
print(getFullName(false)) -- prints: The Doctor
createEffect
createEffect(effect: (scope) -> ()): dispose
Creates a reactive side effect.
- Eager evaluation
- The
scopecan be used to automatically and reactively track updates to dependencies
local dispose = createEffect(function(scope)
print(`Their real name is {getFullName(scope)}`)
end)
-- prints: Their real name is The Doctor
batch(function()
setFirstName("David")
setLastName("Tennant")
end)
-- prints: Their real name is David Tennant
dispose()
[!WARNING] You MUST store a strong reference to the
disposefunction returned fromcreateEffectfor the effect to be guaranteed to re-run. Not storing a strong reference to this function means the effect is liable to be garbage collected.
Implementation
The reactive graph uses a pull-based lazy evaluation model with automatic dependency tracking via the scope mechanism. When a getter is called with a scope, the source registers itself with the observing computed or effect. Updates propagate through the graph and are coalesced via the scheduler to avoid redundant recomputation.
For more detail on the algorithms, see:
- Reactive Algorithms (Reactively)
- Monotonic Painting
Contributing
See CONTRIBUTING.md for development setup, coding standards, and how to submit changes.
Acknowledgements
This library builds upon the existing work on reactive programming, particularly drawing inspiration from S.js, Reactively, Fusion, and jotai.
License
This project is licensed under the terms of the MIT license.
Package Details
Install command (Click to copy)
Version
0.9.1
License
MIT
Safe for commercial use
Automated license review — not legal advice.
