Usage
Reactive is a reactive programming framework for the browser. It gives you a small, predictable model of state, calculations, reverse calculations, control flow and effects, plus a plain DOM driver to project that model onto the page.
The idea
Most frameworks bundle a reactive core together with a large user-interface library: components, layout, widgets, styling. Reactive keeps only the core. It offers a simplified DOM driver that deals in tags, attributes and text. That makes it small, easy to reason about, and simple to drop into an existing page.
The result is a framework you plug in. Mount a reactive component into any element and leave the rest of the page exactly as it is.
Where it comes from
Reactive is a small, hand-written JavaScript library, built from scratch for the browser. Its model, reactive values with readable and writable halves, forward calculations, reverse calculations, and lock-aware readiness, is drawn from a larger reactive system, then refined and tuned to fit JavaScript. The result is a focused runtime you can read in an afternoon.
The runtime primitives live under one object, Reactive: State, Set,
Mount, If, Reverse and the rest. Library modules are their own top-level
globals: you declare one with Reactive.Module, then call it directly, such as
Content.Title(...) or Maths.Add(...). The surface stays small and it runs straight in
the browser with no build step and no imports.
What makes it different
Four choices set Reactive apart. Each links to its full page.
A read lock and a write lock. Every value is boxed. The read is the value and one read lock. The write refers to a state or a reverse, and its lock is a counter. A lock carries no error and no retry. Those belong to the application. A calculation uses a read-locked value, and its result is read-locked. The program does not look at the lock. A driver does.
Reversibility in place of event handlers. Interaction is a
write. Setting a reverse runs its block, and Reactive.Set carries that write out to state. You
understand a control by reading the output or the binding it was given.
Contexts. A context is a set of functions whose implementation
is supplied where the program runs. Reactive.With installs one implementation for a region. A body
in that layer calls the implementation underneath, not the sibling written beside it.
Drivers. Each driver implements one context and is injected before
the program runs. A call of that context enters the driver during the refresh. Maths, strings, Logic.Maybe,
and arrays are those calls. The driver also registers the native types.
Principles
- Small surface. A handful of runtime primitives under
Reactive, plus focused modules as their own globals, run straight in the browser. - Plain DOM. A driver over tags, attributes and text.
- One run. A set runs the program again from the start. State is picked back up from the
same slot. An
Eachscope is picked back up by the element's stable id. - Locks travel with the value. A read lock spreads through every calculation that reads it, and through any structure or array built from it. A field has no lock of its own.
- The program does not see a lock. Error and retry are application state. A driver sees the boxed value and the lock, and decides what to show.
- Local reasoning. You think about one function; the framework does the global reasoning that ties the program together.
- Write once, drive anywhere. The same calls run against whichever driver was injected for
that context, or against the implementation a
Withinstalled. - Composable. Focused modules you add as you need them, snapping together for reuse.
Coding standards
Reactive programs are written in a deliberately small, strict subset of JavaScript. Keeping to it lets the framework read your program, reason about readiness, direction and identity for you, and keep the code tidy as it grows. The rules are few:
- Every value is reactive. Do arithmetic and string work through the provided modules:
Maths,Text, and the rest, which give you a rich library of operations that already understand reactive values. You should almost never need to write a raw calculation yourself. - Name every step; keep calls flat. Pass a name, not a nested function call, as an argument.
Bind each intermediate to a
const, one operation per line, and pass the names along. Naming every value keeps the program legible and analysable. - Reach into structures with a dot.
user.namereads a field. When the value is state, the field is also writable. Writing the field stores the struct. A field has no lock of its own. - Interaction writes an output or a binding. A button receives an
Outputand the value it sets. A slider receives aBinding. To do several things at once, point the button at a reverse. - Declare, then lay out. Bring your state and derived values into being at the top of a component, then describe the interface underneath, so a component reads top to bottom.
- Callbacks get a block. Write a closure with a braced body,
() => { … }, one statement per line. Every callback is a small function, and looking like one keeps the program easy to read.
Reactive.Function(Collection.Array.Of(Ui.Component), "Render", () => {
const children = Reactive.Hole("children");
Reactive.With(() => {
Content(() => {
Text((text) => {
const textValue = Ui.Value.text(text);
const attributes = Collection.Map.Gather(() => {
Collection.Map.Gather.Entry("text", textValue);
});
const component = Ui.Component("Ui.Text", attributes);
Reactive.Emit("components", component);
});
});
children();
});
const components = Reactive.Gather(Collection.Array.Of(Ui.Component), "components");
return components;
});