Skip to content

Reactivity

Introduction

Reactivity has been a hot buzzword for modern JavaScript UI frameworks in the past few years. Angular, Vue, and Svelte all have reactivity built-in. They are famous and popular because of their reactivity features.

Reactivity means that the changed application state will automatically reflect in the DOM.

Don't be confuse the reactivity with another buzz word, reactive programming. Reactive programming is programming with asynchronous data streams. I will have another post to explain reactive programming.

Reactivity is related to the data binding concept. Data binding is the process that establishes a connection between the application state and the application UI. There are two major types of data binding: one-way bing and two-binding.

  • One-way binding means that changes in the application state cause changes to the application UI.

  • Two-way binding means that either application state or application UI changes (for example, with input elements) automatically update the other.

The reactivity also applies to the state object properties. E.g., if there is a person object with the properties of first-name, last-name, and full-name, we want the full-name property to be reactive to the other two name properties.

With the reactivity concept clarified, let's see how we can have reactivity in AppRun.

One-Way

Many frameworks use the concept of "variable assignments trigger UI updates." E.g., Vue wires up the application state objects with a change detection mechanism to become a view model or Proxy. Then you can modify the view model to trigger the UI update. Svelte has a compiler to inject change detection around your application state object. You can also modify the state to trigger the UI update.

Unlike other frameworks, AppRun uses the events to trigger UI updates following the event-driven web programming model naturally. During an AppRun event lifecycle:

  • AppRun gives you the current state for you to create a new state
  • AppRun calls your view function to create a virtual
  • AppRun renders the virtual DOM if it is not null.

You can feel the Hollywood Principle (Don't call us. We call you.) here, which usually means things are loosely coupled. We provide code pieces. The framework calls them when needed.

In the example below, the AppRun $onclick directive calls the event handler, then calls the view function, and then renders the virtual DOM.

const view = state => <div>
  <h1>{state}</h1>
  <button $onclick={state => state - 1}>+1</button>
  <button $onclick={state => state + 1}>+1</button>
</div>;

app.start(document.body, 0, view)

Two-Way Binding

AppRun $bind directive can update the state properties automatically when used with the input elements and the textarea element. It looks similar to Angular's ngModel, Vue' v-model, and Svelte's bind:value syntax. However, Angular, Vue, and Svelte have invented their own proprietary template language/syntax that you need to learn. AppRun uses the JSX that React also uses.

const view = state => <>
  <div>{state.text}</div>
  <input $bind="text" placeholder="type something here ..."/>
</>
app.start(document.body, {}, view)

Reactive State

The state properties' reactivity is not a problem that the UI frameworks are to solve. But if the UI frameworks wrap or change the original state objects, they have to solve the reactivity problems. E.g., Vue uses the computed object. Svelte uses the reactive-declarations, the famous $: sign.

Property Getter

Like in languages like Java and C#, JavaScript has object property getter, which we can use to compute the property values dynamically.

const state = ({
  a: 1,
  b: 2,
  get c() {
    return this.a + this.b;
  }
})

Binding to the state object properties is straightforward.

// Reactivity - getter
const state = {
  a: 1,
  b: 2,
  get c() {
    return this.a + this.b;
  }
};
const view = ({a, b, c}) => <>
  <input type="number" $bind="a" />
  <input type="number" $bind="b" />
  <p>{a} + {b} = { c }</p>
</>;
app.start(document.body, state, view);

ES2015 Proxy

The Proxy is used to define custom behavior for fundamental operations (e.g., property lookup, assignment, enumeration, function invocation, etc.).

Proxies enable you to intercept and customize operations performed on objects (such as getting properties). They are a metaprogramming feature. - from Metaprogramming with proxies

To create a Proxy, we create a handler first. Then, we combine the object proxied with the handler.

const handler = ({
  get: (target, name) => {
    const text = target.text || '';
    switch (name) {
      case 'text': return target.text;
      case 'characters': return text.replace(/\s/g, '').length;
      case 'words': return !text ? 0 : text.split(/\s/).length;
      case 'lines': return text.split('\n').length;
      default: return null
    }
  }
})

const state = new Proxy(
  { text: "let's count" },
  handler
)

Proxy has almost no barrier to use. Anywhere accepts objects can use Proxy. For example, AppRun can take a state with Proxy.

// Reactivity - Proxy
const handler = {
  get: (target, name) => {
    const text = target.text || '';
    switch (name) {
      case 'text': return target.text;
      case 'characters': return text.replace(/\s/g, '').length;
      case 'words': return !text ? 0 : text.split(/\s/).length;
      case 'lines': return text.split('\n').length;
      default: return null
    }
  }
};
const state = new Proxy(
  { text: "let's count" },
  handler
);
const view = state => <div>
  <textarea rows="10" cols="50" $bind="text"></textarea>
  <div>chars: {state.characters} words: {state.words} lines: {state.lines}</div>
  <pre>{state.text}</pre>
</div>;
app.start(document.body, state, view);

I like Proxy because it takes the property value calculation logic out of the state objects. As a result, the proxy handler is much easier to test and maintain. On the other hand, the state objects stay lean. I want the state to act like the data transfer object (DTO) in traditional multi-layered application architecture, where the DTO is an object that carries data between logical and physical layers.

Conclusion

AppRun has full reactivity support that provides us the one-way and two-way data binding and the reactive state. Thus, we only need to use the native JavaScript/TypeScript features. Furthermore, AppRun does not require you to learn a new language or a new templating syntax.