Introduction
Reactivity has been a hot buzz word for modern JavaScript UI frameworks in the past few years. Angular, Vue, and Svelte all have the reactivity builtin. 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 of the application state cause changes to the application UI.
Two-way binding means that changes of either application state or application UI (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 that has 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 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)
See live demo: https://apprun.js.org/#play/8
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)
See live demo: https://apprun.js.org/#play/0
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.
I prefer to only use the native JavaScript/TypeScript features.
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.
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)
See live demo: https://apprun.js.org/#play/17
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 tobe 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. AppRun can accept a state with Proxy.
const view = state => <div>
<textarea rows="10" cols="50" $bind="text"></textarea>
<div>{state.characters} {state.words} {state.lines}</div>
{state.text}
</div>
app.start(document.body, state, view)
See live demo: https://apprun.js.org/#play/18
I like Proxy because it takes the property value calculation logic out of the state objects. The proxy handler is much easier to test and maintain. 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 as well as the reactive state. We only need to use the native JavaScript/TypeScript features. AppRun does not require you to learn a new language or a new templating syntax.
Top comments (0)