• This repository has been archived on 12/Sep/2022
  • Stars
    star
    209
  • Rank 188,325 (Top 4 %)
  • Language
    JavaScript
  • License
    Other
  • Created over 13 years ago
  • Updated over 7 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A library for programming reactively in Javascript.

Reactive.js

A (really) short intro

Reactive.js will augment ("reactify") a given Javascript function so that it may track dependencies on other reactive functions. You might think of a reactive function as representing a cell in a spreadsheet. Spreadsheet cells can reference each other, depend on each other, and cells update their values automatically when one of their dependent cells change. Reactive functions can do this too. To be clear, they're still Javascript functions, just with a little "extra".

Note that Reactive.js' take on functional-reactive programming is discrete and push-based, which differs from some other FRP libraries.

Reactive Programming in 60 seconds

In Reactive programming it's easier to think of our variables as expressions, not as assignments. In order to understand the difference, consider the statement "a = b + c".

There are two ways to look at this. One is the way we are used to, "a is assigned to the sum of b and c, at the instant this is interpreted", which is what we'd expect in Javascript or any other imperative language.

But we could also read it as "a represents the sum of b and c, at any point in time." This interpretation is not really so strange, that's exactly how we would expect things to work in a spreadsheet. If we had a spreadsheet cell containing the expression "=B+C", the value in that cell would change as the cells B and C change, wouldn't it?

Reactive programming means we describe our program using the second interpretation. We don't assign variables, we express them, and they don't represent discrete values, they represent a value that changes over time.

Reactive programming in Javascript

Obviously Javascript is not a reactive language, but there are advantages to being able to express ourselves reactively. Reactive programming can be immensely useful in describing systems of equations, complicated UIs, and data visualizations to name a few.

So let's reexamine our earlier statement about reactive programming, then we'll see how Javascript fits into the picture (and how Reactive.js fits into Javascript).

"We don't assign variables, we express them..."

In Javascript, a = b + c assigns a value. For us to accomplish our goal, however, we need to describe what a represents using an expression (like =B+C in a spreadsheet). Javascript does have expressions, they're called functions! So in reactive programming a given value, like a in a = b + c is expressed as a function:

	var a = function (b,c) { return b + c } // a = b + c

This brings us to our first conclusion.

Conclusion 1: Our variables are expressions, so our variables are functions.

Now lets consider the rest of the sentence:

"…and they don't represent discrete values, they represent a value that changes over time"

When you write =B+C in a spreadsheet, your spreadsheet program notes that your cell is relying on the values of B and C. It starts to assemble a dependency graph internally that it can use to keep track of changes. It traverses that graph when B or C change, updating A in the process. Most importantly, we don't have to write a "calculate all" function because the spreadsheet program handles that for us.

Unfortunately Javascript won't magically track dependencies, so it's not enough to describe our variables as expressions, we also need to tell our expressions what they depend on. Only then can they be smart enough to update each other automatically

Conclusion 2: We have to tell our expressions what they depend on.

When we combine our two conclusions, we arrive at the following:

Our variables are expressions, so our variables are functions. And we have to tell our expressions what they depend on, so that means we have to tell our functions what they depend on.

Fortunately for you, Reactive.js does just that.

Using Reactive.js

At its core, Reactive.js is just a single method, $R(). $R() accepts a function and returns you an augmented version of that function that is meant to represent a value in your program. How is it augmented exactly? "Reactive functions" gain a new method called .bindTo(). bindTo() accepts one or more reactive functions, and binds them to your function's arguments via partial application.

Don't worry about $R.state yet, it just returns a reactive function that gives you a way to maintain and retrieve an internal state value (like a cell in a spreadsheet that doesn't start with =).

	var a = $R.state(1);
	var b = $R.state(2);
	var c = $R(function (v1, v2) { return v1 + v2 }); // C = A + B
	c.bindTo(a, b); // Tell C it depends on A and B

	c() // -> 3

	a.set(5)   // Set A to 5
	b.set(10)  // Set B to 10
	c() // -> 15

That's it. As you can see, Reactive.js asks you to express values as functions and gives you the tools you need to tell those functions how they depend on each other. In the example above, any time a or b change, c will change too. c isn't assigned a + b, it represents a + b at any moment in time.

An example with time

Since we talk about variables representing a value that changes over time, let's actually create variables that depend on, well, a value that changes over time.

	var now = $R(function (date) { return date });

	//Update "now" every millisecond
	setInterval(function () { now(new Date) }, 1);

	var currentMillisecond = $R(function (date) {
		return date.getMilliseconds() ;
	}).bindTo(now);

	var dayOfTheWeek = $R(function (date) {
		var days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
		return days[date.getDay()];
	}).bindTo(now);

	var isItSunday = $R(function (day) {
		return day === 'Sun';
	}).bindTo(dayOfTheWeek);

We can log out any of our reactive variables at any time and we'll see that they're staying current as the value of now is updated.

	console.log(currentMillisecond()); //-> 700
	console.log(currentMillisecond()); //-> 800
	console.log(currentMillisecond()); //-> 900
	console.log(dayOfTheWeek()); //-> Thu
	console.log(isItSunday()); //-> false

	//And that Sunday...
	console.log(currentMillisecond()); //-> 400
	console.log(dayOfTheWeek()); //-> Sun
	console.log(isItSunday()); //-> true

Values-as-functions are purely conceptual

Not every value in your code is represented as something you can return. As one example, we can still think of the content of the div in the following example as a single value:

	<div id="time"></div>
	var timeDivContents = $R(function (now) {
		$("#time").html(now.toString());
	}).bindTo(now);

We didn't create a function that "updates the contents of the time div", we're saying this function represents the contents of the time div. As the setInterval statement updates "now" every millisecond our div will update too, all on its own.

Reactive.js is minimal

Reactive.js seeks to be as minimal and unobtrusive as possible. Because it operates on, and returns, normal Javascript functions, it's very easy to integrate into existing code. If you start writing "reactive" code, any existing function can be integrated as a dependency by creating a reactive version of it with $R().

Reactive.js API

$R(function[, context]) ➑ ReactiveFunction

$R() takes a function and optional context. If supplied a context, Reactive.js will ensure that your function runs with this bound to context.

$R() returns a new function that is functionally identical, but with augmentation. In short, $R() returns a "reactified" version of your function, giving it the ability to participate in the dependency graph of reactive values in your application. A reactive function may bind (via .bindTo()) its arguments to other reactive functions, and vice versa.

	//Unbound reactive functions are functionally identical
	var sum = function (a,b) { return a + b };
	var reactiveSum = $R(sum);
	reactiveSum(1,2); //-> 3

rFnc.bindTo(ReactiveFunction,…) ➑ rFnc

When you supply a function to $R() it returns a new function with the addition of a bindTo() method. bindTo() takes one or more reactive functions as its arguments. The provided functions are bound to the arguments in your function (partial application). When those functions' values update they will be passed as the arguments to your function, which will re-calculate itself (and pass its value to any other functions that depend on it in turn).

	var x = $R(function (n) { return n });
	var x2 = $R(function (x) { return x * x }).bindTo(x);

	x(2);
	x2(); //->4
	x(4);
	x2(); //->16

bindTo() can also accept literal values. It will bind them as you would expect:

	var greet = $R(function (name) {
		return "Hello "+ name
	}).bindTo("Jane");
	console.log(greet()); //-> "Hello Jane"

bindTo() can accept a mix of literals and reactive functions.

$R._

$R._ is a special value that represents a "gap" when binding up the arguments in your reactive function. For example, if we want to bind the first and third arguments of a function, but not the second, we would say:

	var reactiveFnc = $R(function (a,b,c) { return a + b + c });
	reactiveFnc.bindTo(myA, $R._, myC);

We bound a and c in our reactive function to myA and myC, so now reactiveFnc only accepts one argument, the argument for b.

$R.state([initial]) ➑ ReactiveFunction

Since Reactive.js uses functions, not variables, to represent values, it's helpful to have a concise way to represent state. $R.state returns a reactive function that gets and sets internal state.

State can be set with set:

	var log = [];
	var num = $R.state();
	var historian = $R(function(history, v) { history.push(v) }).bindTo(log, num)

	num.set(3);
	num.set(4);
	log //-> [3, 4]

Or modified by passing a transform function to modify:

	var log = [];
	var num = $R.state();
	var historian = $R(function(history, v) { history.push(v) }).bindTo(log, num)

	num.set(10);
	num.modify(function(v) { return v + 1 });
	log // -> [10, 11]

Notes on internals

There are a few things to throw out there for the curious:

###V alues are cached Reactive functions cache their values when they are executed. Consider a function X that depends on functions Y and Z (which have already run). If we run function X if will ask for the values of it dependencies X and Y, however X and Y will not be run because they have not changed. This is important for large systems of interdependent reactive functions. And, of course, if a dependency's value changes its cached value is updated and any downstream dependent values will execute and update their cached values too.

Functions are not evaluated redundantly

Reactive.js performs a topological sort of the dependency graph, meaning it assembles a list of functions to evaluate in-order. Reactive.js is smart enough to ensure that a function, even if it is a dependency for several others, will only be executed once.

Dependency graph traversal is recursive

In a worst case scenario, with a chain of dependent nodes thousands in number, we can exceed the size of the call stack. You'd really have to try at this to do it, but the limitation is out there. If it's truly an issue the traversal function can be swapped for a loop in the future.

Where is it being used?