I remember Backbone using two-way bindings, which I think is more complicted than what my experiment does:
const el = document.createElement(tag)
for (const [k, v] of Object.entries(attrs)) {
if (v instanceof Ref) {
el [k] = v.val
v.watch(val => el[k] = val)
}
else {
el[k] = v
}
}
You might be thinking of Knockout.js. Backbone used a manual event-driven approach where you'd wire up each component to a model/observer that you called .set(value) on. Except it was a lot more verbose, and because you'd be using chunks of string templates for rendering, it wasn't very fine grained — basically how far up the tree you added the subscriptions defined the update granularity. Most importantly, no incremental rendering or virtual DOM meant manual management for any input states, and the lack of a defined render lifecycle meant it was hard to keep track of updates and very easy to get into infinite recursion loops.
As someone else mentioned in this thread, the main innovation in the React era was actually it's sister library Flux which brought the idea of a unidirectional data flow into the picture, later inspiring Redux et al. We are now getting past the immutability detour and keeping the best of both worlds, maybe.