Scopes

Scopes contain a block of nodes which are fixed in place. You can add and remove whole scopes in one step for the dynamic parts of the program, for example for conditional logic (Reactive.If) or array iteration (Reactive.Each). The root scope is the first. Scopes nest inside one another and form a tree.

The Scope Tree

For scopes to nest, each one records the node that owns it: a parentNode, the node object whose block this scope is. An If owns its body scope. That is what the next step builds. The parentNode from any scope climbs the tree towards the root.

{
  nodes: [],       // the nodes built into this scope
  parentNode,      // the node object that owns this scope
};
A scope now knows its owner, so scopes form a tree

The root scope has no owning node. Its parentNode is undefined. A climb up the tree stops the moment it reaches a parent that is not there. As a result, that undefined parent is where every climb ends.

_scopeCreate

A new scope now takes the owning node and stores it on the new scope. There are no ids and no map: the scope is just an object, and _scopeCreate returns it directly. The ambient _currentScope is the scope object itself:

Reactive._scopeCreate = (parentNode, callback) => {
  const scope = { nodes: [], parentNode };
  const previousScope = Reactive._currentScope;
  Reactive._currentScope = scope;
  try {
    callback();
  } finally {
    Reactive._currentScope = previousScope;
  }
  return scope;
};
A scope is opened under an owning node

Reactive.Root

You open the root scope like any other, so Reactive.Root gives it an undefined parent. It has no owning node, which is what makes the climb stop there. The scope object it opens is kept as root.rootScope:

Reactive.Root = (programCallback) => {
  const previousRoot = Reactive._currentRoot;
  const root = {};
  Reactive._currentRoot = root;
  try {
    root.rootScope = Reactive._scopeCreate(undefined, programCallback);
    return root;
  } finally {
    Reactive._currentRoot = previousRoot;
  }
};
The root scope is opened with no owning node

Running It

The program still builds into the root scope. But that scope now records its parent, the undefined owner that roots the tree:

const root = Reactive.Root(() => {
  const one = Reactive.Constant(1);
  const two = Reactive.State(2);
  const sum = Reactive.Calculate([one, two], (a, b) => a + b);
});

const rootScope = root.rootScope;
rootScope.parentNode;   // undefined   no owning node; the climb stops here
The root scope sits at the top of the tree, with no owning node

Next Steps

With the scope tree in place, you can treat a section of the program as a unit and hang it off the node that owns it. Next, If builds a nested scope, owned by the If node, only when a condition holds.