Hi guys!
My name is Dan. This is the second article about my project.
In the first article I talked about how state management can be organized using WhatsUp. Today I will tell you how easy and simple it is to organize the display of data in a react-like way - with jsx.
JSX
WhatsUp has its own jsx-renderer. There are two packages for this - @whatsup/jsx
& @whatsup/babel-plugin-transform-jsx
. And a simple HelloWorld would look like this.
import { cause } from 'whatsup'
import { render } from '@whatsup/jsx'
const app = cause(function*(){
while(true){
yield <div>Hello world</div>
}
})
render(app)
His work is based on mutators. Unlike React, WhatsUp never creates the entire virtual house as a whole for further comparison and modification. It incrementally modifies the thread-specific fragment. In addition, WhatsUp can render directly to the body element (by default).
Fragments and their syntax (<></>
) are also supported, and components are only defined as pure functions, have no internal state, and are only responsible for markup.
Fractals
This is perhaps the main component of my framework. Looking for inspiration, I looked on YouTube a lot of videos of traveling to fractals, for example one, two, three. Just take a look. Imagine that you are flying through the universe in a spaceship. It's incredibly beautiful!
There is mathematics behind all this beauty. If you look for a long time, you will notice some patterns. The fractal is designed in such a way that if we do not pay attention, then we do not notice the seams between the repeating patterns. They are not the same repeating pattern. Each time this is a new pattern, created in the context of the previous one according to the same rules.
Now imagine that our ship is not moving, it stands still, and the fractal gives us a stream of information towards us. This information reflects the internal state of the fractal. I really hope that I managed to describe it correctly. Let's move on to practice.
const app = fractal(function*(){
while(true){
yield <div>Hello world</div>
}
})
It looks like Cause
, right? This is true, the only difference is that for each consumer, the fractal creates a personal generator and context. The contexts connect to each other as parent-child and form a context tree. This allows you to organize the transfer of data down the tree, as well as the bubbling of events up. A cause, unlike a fractal, creates one iterator for all consumers, and one context without a reference to the parent (root context).
Sharing
This mechanism allows you to move data down the context. Any data shared somewhere in parent fractals will be available in child fractals. To do this, in the context there are methods ctx.share()
, ctx.get()
and ctx.find()
.
import { fractal } from 'whatsup'
import { render } from '@whatsup/jsx'
class Theme {
readonly name: string
constructor(name: string) {
this.name = name
}
}
const app = fractal(function* (ctx) {
const theme = new Theme('dark')
ctx.share(theme)
// share theme instance to children fractals
while (true) {
yield <div>{yield* page}</div>
}
})
const page = fractal(function* (ctx) {
const theme = ctx.get(Theme)
// get theme instance from parent fractals
while (true) {
yield (
<div>
Theme name is <b>{theme.name}</b>
</div>
)
}
})
render(app)
The ctx.get()
method searches for data based on a strict constructor match, but ctx.find()
method uses instanceof
.
class User {}
class Guest extends User {}
const guest = new Guest()
// then in parent fractal
ctx.share(guest)
// and in child fractal
const user = ctx.find(User)
// ^^^^
// the user constant will contain the Guest instance
There is another way to share data - factors.
import { factor } from 'whatsup'
const Token = factor(null)
// default value ^^^^ if factor is not found
// then in parent fractal
ctx.share(Token, 'i am a secret token')
// and in child fractal
const token = ctx.get(Token)
//> 'i am a secret token'
A factor is a key by which you can share data in a context and get data out of a context.
Event system
This system allows sending events between fractals up the context. To work with events in context, there are ctx.on()
,ctx.off()
and ctx.dispatch()
methods.
import { fractal, Event } from 'whatsup'
import { render } from '@whatsup/jsx'
class TimeEvent extends Event {
readonly time: number
constructor(time: number) {
super()
this.time = time
}
}
const app = fractal(function* (ctx) {
ctx.on(TimeEvent, (e) => console.log('Time', e.time))
// attach listener for TimeEvent instances
while (true) {
yield <div>{yield* page}</div>
}
})
const page = fractal(function* (ctx) {
const onClick = () => {
const event = new TimeEvent(Date.now())
ctx.dispath(event)
// trigger event bubbling
}
while (true) {
yield (
<div>
<button onClick={onClick}>Print time</button>
</div>
)
}
})
render(app)
Look at an example - when you click on the button, a message with the current time will appear in the console.
The ctx.off()
method is needed to remove the event handler. In most cases, it does not need to be called manually - all handlers are automatically removed when the fractal is destroyed.
Extended example
The fractal
function is shorthand for creating fractals. There is a base class Fractal
available for extension. When extending, we need to implement a whatsUp
method that returns a generator.
The following example demonstrates the use of the event system and data sharing.
import { Fractal, Conse, Event, Context } from 'whatsup'
import { render } from '@whatsup/jsx'
class Theme extends Conse<string> {}
class ChangeThemeEvent extends Event {
readonly name: string
constructor(name: string) {
super()
this.name = name
}
}
class App extends Fractal<JSX.Element> {
readonly theme = new Theme('light');
readonly settings = new Settings()
*whatsUp(ctx: Context) {
// sharing this.theme for child fractals
ctx.share(this.theme)
// attach ChangeThemeEvent event listener
ctx.on(ChangeThemeEvent, (e) => this.theme.set(e.name))
while (true) {
yield (<div>{yield* this.settings}</div>)
}
}
}
class Settings extends Fractal<JSX.Element> {
*whatsUp(ctx: Context) {
// get Theme from context
const theme = ctx.get(Theme)
// dispatch ChangeThemeEvent on click button
const change = (name: string) =>
ctx.dispatch(new ChangeThemeEvent(name))
while (true) {
yield (
<div>
<h1>Current</h1>
<span>{yield* theme}</span>
<h1>Choose</h1>
<button onClick={() => change('light')}>light</button>
<button onClick={() => change('dark')}>dark</button>
</div>
)
}
}
}
const app = new App()
render(app)
Errors catching
Throwing an exception at the framework level is common. The error propagates along the streams, like any other data, and is handled by the standard try {} catch {}
construction. In this case, the reactivity system preserves the state of dependencies in such a way that when the situation is corrected and the error disappears, the threads recalculate their data and return to their normal operating state.
import { conse, Fractal } from 'whatsup'
import { render } from '@whatsup/jsx'
class CounterMoreThan10Error extends Error {}
class App extends Fractal<JSX.Element> {
*whatsUp() {
const clicker = new Clicker()
const reset = () => clicker.reset()
while (true) {
try {
yield <div>{yield* clicker}</div>
} catch (e) {
// handle error
if (e instanceof CounterMoreThan10Error) {
yield (
<div>
<div>Counter more than 10, need reset</div>
<button onClick={reset}>Reset</button>
</div>
)
} else {
throw e
}
}
}
}
}
class Clicker extends Fractal<JSX.Element> {
readonly count = conse(0)
reset() {
this.count.set(0)
}
increment() {
const value = this.count.get() + 1
this.count.set(value)
}
*whatsUp() {
while (true) {
const count = yield* this.count
if (count > 10) {
throw new CounterMoreThan10Error()
}
yield (
<div>
<div>Count: {count}</div>
<button onClick={() => this.increment()}>increment</button>
</div>
)
}
}
}
const app = new App()
render(app)
I tried to design the framework in such a way that it was easy and intuitive to use it.
Performance
Performance is very important. I added whatsapp to the krausest/js-framework-benchmark project. Below is an excerpt from the results table showing WhatsUp position against the background of the most popular libraries and frameworks such as Inferno, Preact, Vue, React and Angular.
In my opinion, the position is already quite worthy.
Conclusion
I hope you enjoyed the article. These are not all the features of the WhatsUp framework. In the next article I will talk about delegation, routing, asynchronous tasks. You can find more examples here, their sources.
If you liked the idea of my framework - leave your feedback or a star on the github, also contributing welcomed. I'll be very happy. Thanks!
Top comments (0)