Note: this chapter is modelled after RectJS/State and Lifecycle article. It is conceptionally close but uses different terminology.
Sciter's Reactor, in general, is all about components that employ Model-View-Controller idiom.
You have some data to be presented to the user. User is able a) to view the data and manipulate it. Manipulation of data is a set of code functions and event handlers that, as a group, are named as Controller.
Let's transform our ticking clock sample into reusable Clock component.
Each Reactor class based component shall have render()
method:
class Clock : Element { function render() { return <div> <h1>Hello, world!</h1> <h2>It is {this.time.toLocaleString(#time)}.</h2> </div>; } }
The render()
method will be called each time an update happens, but as long as we render <Clock />
into the same DOM node, only a single instance of the Clock
class will be used. This lets us use additional features such as local state and lifecycle methods.
By convention local data storage of a component is located in instance's this.data
field:
class Clock: Element { // declaring local data storage with initial value this var time = new Date(); function render() { return <div> <h1>Hello, world!</h1> <h2>It is {this.time.toLocaleString(#time)} now.</h2> </div>; } }
and our render()
method renders now content of this
properties.
Class Clock, in its current version, just renders static time determined at the time of its creation. Let's transform it to a running clock.
We want to set up a timer whenever the Clock
is rendered to the DOM for the first time - when real DOM element is created. In ReactJS this is called “mounting”. Sciter uses "attached" term for that - function attached() is called when instance of a class is attached to the DOM element:
include "sciter:reactor.tis"; class Clock: Reactor.Component { this var time = new Date(); // setting initial state function attached() { // a.k.a. React::componentDidMount this.timer(1s, function() { this.update { time:new Date() }; return true; // to keep the timer ticking }); } function render() { return <div> <h1>Hello, world!</h1> <h2>It is {this.time.toLocaleString(#time)} now.</h2> </div>; } }
You see here two new entities:
Reactor.Component
class that is declared in sciter:reactor.tis script.attached()
method that starts ticking timer.The timer callback function is where the magic happens:
function() { this.update { time:new Date() }; return true; // to keep the timer ticking }
On each timer tick it calls Reactor.Component.update(newdata)
method with the new data.
Note: Sciter out of the box provides two lifecycle methods:
attached()
- called by runtime when the element is attached to the DOM tree anddetached()
- when the element is deleted from the DOM tree. In this particular sample we are not using it if to compare our Clock with the React's one. Timers in Sciter are always created for concrete DOM elements, as soon as element is destroyed all its timers are freed too.And now it is a time to check what that Reactor.Component
class is.
The Reactor.Component
class is defined in "sciter:reactor.tis" resource that is an integral part of Sciter Engine, so it is always available.
The class is very simple - it introduces just one method update(newdata)
:
namespace Reactor { class Component : Element { function update(newdata = null) { assert !newdata || typeof newdata == #object; if(newdata) this.extend(newdata); this.post(() => this.merge(this.render()),true); } } }
As you see the update(newdata)
does the following:
this.extend(newdata)
- this merges newdata with properties of this object;function updater() { this.merge(this.render()); }
- defines local update method that this.render()
method that produces virtual element;Element.merge(velement)
method that patches this existing DOM element using velement definition;this.post(updater)
- schedules the updater()
invocation to the next event processing cycle.The delayed rendering logic ensures that multiple updater()
execution requests are collapsed into single updater()
invocation request in the event queue.
There are three things you should know about update(newdata)
.
For example, this will not re-render a component:
// WRONG! this.comment = "Hello";
Instead, use update():
// Correct this.update {comment: "Hello"};
The only place where you can assign properties directly is the constructor.
When you call update(newdata)
, Reactor.Component
merges the object you provide into the current data set.
Therefore your code may issue update()
requests for different parts of the data and they will end up in single reconciliation action between real DOM element and result of render()
call, so these:
clock.update { time:new Date() }; ... clock.update { greeting: "Вася" };
will end up in single clock.render()
call and screen update.
Comment to the above. In Sciter, this:
obj.method { name:val };is a short form of a method call:
obj.method({ name:val });Just a syntax sugar for making our developer's life a bit easier.