According to some sources, back in IV BC, Aristotle asked one simple question - What happened before? Chicken or egg? He himself eventually came to the conclusion that both appeared at the same time - this is a twist! Is not it?
, , - . , , , β , , β , (, ..).
. . . , , .
. β , . β . ?
What's up guys?
- - .
npm i whatsup
β . . api. .
, react
+ mobx
, , 5kb gzip
.
, β , , . . β , , , , .
Cause & Conse
. . computed
observable
, , , , .
. , ( , β ).
const name = conse('John')
// - What`s up name?
whatsUp(name, (v) => console.log(v))
// :
//> "John"
name.set('Barry')
//> "Barry"
, ? conse
, whatsUp
β "" . .set(...)
β β .
Conse
Cause
. , yield*
β "" , , , yield*
return
( yield
, )
const name = conse('John')
const user = cause(function* () {
return {
name: yield* name,
// ^^^^^^ name
//
}
})
// - What`s up user? :)
whatsUp(user, (v) => console.log(v))
// :
//> {name: "John"}
name.set('Barry')
//> {name: "Barry"}
yield* name
user
name
, , β name
β user
β β .
?
, - "". . , user
revision
, .
β revision
, user
.
const name = conse('John')
let revision = 0
const user = cause(function* () {
return {
name: yield* name,
revision: revision++,
}
})
whatsUp(user, (v) => console.log(v))
//> {name: "John", revision: 0}
name.set('Barry')
//> {name: "Barry", revision: 1}
- , β revision
. β , ( ) yield
return
, , .
const name = conse('John')
const user = cause(function* () {
let revision = 0
while (true) {
yield {
name: yield* name,
revision: revision++,
}
}
})
whatsUp(user, (v) => console.log(v))
//> {name: "John", revision: 0}
name.set('Barry')
//> {name: "Barry", revision: 1}
? , , . revision
, , . revision
, β .
cause
conse
β . , .
import { Cause, Conse, whatsUp } from 'whatsup'
type UserData = { name: string }
class Name extends Conse<string> {}
class User extends Cause<UserData> {
readonly name: Name
constructor(name: string) {
super()
this.name = new Name(name)
}
*whatsUp() {
while (true) {
yield {
name: yield* this.name,
}
}
}
}
const user = new User('John')
whatsUp(user, (v) => console.log(v))
//> {name: "John"}
user.name.set('Barry')
//> {name: "Barry"}
whatsUp
, .
whatsUp
. , update
β .
. , , . , try {} finally {}
.
-, 1 , setTimeout
, , clearTimeout
.
const timer = cause(function* (ctx: Context) {
let timeoutId: number
let i = 0
try {
while (true) {
timeoutId = setTimeout(() => ctx.update(), 1000)
// 1
yield i++
//
//
}
} finally {
clearTimeout(timeoutId)
//
console.log('Timer disposed')
}
})
const dispose = whatsUp(timer, (v) => console.log(v))
//> 0
//> 1
//> 2
dispose()
//> 'Timer disposed'
β
, . .
const increment = mutator((i = -1) => i + 1)
const timer = cause(function* (ctx: Context) {
// ...
while (true) {
// ...
//
yield increment
}
// ...
})
β , . , . , undefined
, i
-1
, 0
. .. increment
i
.
. , , ===
. β . , , , . - , , , .
class EqualArr<T> extends Mutator<T[]> {
constructor(readonly next: T[]) {}
mutate(prev?: T[]) {
const { next } = this
if (
prev &&
prev.length === next.length &&
prev.every((item, i) => item === next[i])
) {
/*
, ,
, ,
*/
return prev
}
return next
}
}
const some = cause(function* () {
while (true) {
yield new EqualArr([
/*...*/
])
}
})
.. , shallowEqual
, , , . , .
cause
conse
mutator
β . , Mutator
, mutate
.
β dom-. β body
, .
class Div extends Mutator<HTMLDivElement> {
constructor(readonly text: string) {
super()
}
mutate(node = document.createElement('div')) {
node.textContent = this.text
return node
}
}
const name = conse('John')
const nameElement = cause(function* () {
while (true) {
yield new Div(yield* name)
}
})
whatsUp(nameElement, (div) => document.body.append(div))
/*
<body>
<div>John</div>
</body>
*/
name.set('Barry')
/*
<body>
<div>Barry</div>
</body>
*/
β WhatsUp β , observable
, computed
, reaction
. action
, . , , .
, :) , . , , , . parent-child
β , . , ! , , .
import { Fractal, Conse, Event, Context } from 'whatsup'
import { render } from '@whatsup/jsx'
class Theme extends Conse<string> {}
class ChangeThemeEvent extends Event {
constructor(readonly name: string) {
super()
}
}
class App extends Fractal<JSX.Element> {
readonly theme = new Theme('light');
readonly settings = new Settings()
*whatsUp(ctx: Context) {
// this.theme
// .. " "
ctx.share(this.theme)
// ChangeThemeEvent,
//
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) {
// Theme, -
const theme = ctx.get(Theme)
// , ctx.dispath
const change = (name: string) =>
ctx.dispath(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)
ctx.share
, - , ctx.get
.
ctx.on
, ctx.dispatch
. ctx.off
, , .
, jsx- babel- jsx-. ? β . , dom-, html-. dom ( react, ) . , . β , , dom Settings
( yield* theme
β ).
, <body>
. render
, , β .
, , .
. , , try {} catch {}
. , , .
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) {
// , "" - ,
//
// -
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)
, , , :
yield*
βyield
βreturn
βthrow
β
β , whatsup
js-framework-benchmark. - , β , : , , , , , . . , whatsup
, inferno
, preact
, vue
, react
angular
, "" , . , , " " β .
-
3 kb gzip. β whatsup
. , 5-.
Glitch free
JustDont :
. , , , . , , 20. maximum call stack size exceeded.
. mobx whatsup ( ). : "", . a
, b
, c
, d
. a2 = b1
, b2 = a1-c1
, c2 = b1+d1
, d2 = c1
. "" . , "".
β Chrome 88.0.4324.104 (64-) mobx
1653 , Maximum call stack size exceeded
. β .
Whatsup
5, 10 100 000 β out of memory
. , . layersCount
.
, . , yield delegate(otherStream)
.
defer
, . , , .
@whatsup/route
β route
redirect
. , , react-router
. , ([0-9]+)
. , .
CLI
β AndrΓ© Lins. whatsup
- .
npm i -g @whatsup/cli
# then
whatsup project
WhatsUp
- react
-. @whatsup/react, .
Todos β TodoMVC
Loadable β , ctx.defer
, , , ,
Antistress β , , . , , . β , β , β . ,
Sierpinski β ,
, whatsup
β , β β¦ - .
Thank you for your time, I hope I took it away from you for a reason. I would be glad to receive your comments, constructive criticism and help in the development of the project. But what is really here - even an asterisk on github is already a reward.
In addition, I want to express my gratitude to all those who supported me, wrote in a personal, by e-mail, in vk, telegram. I did not expect such a reaction after the publication of the first article, it was a pleasant surprise for me and an additional incentive for the development of the project. Thanks!
Best regards, Denis Ch.
"Most of my work is the agony of the birth of a new scientific discipline" Benoit Mandelbrot