
Nach einigen Quellen stellte Aristoteles bereits im Jahr IV v. Chr. Eine einfache Frage: Was ist vorher passiert? Huhn oder Ei? Er selbst kam schlieĂźlich zu dem Schluss, dass beide gleichzeitig auftraten - das ist eine Wendung! Oder?
, , - . , , , — , , — , (, ..).
. . . , , .
. — , . — . ?
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
— , — … - .
Vielen Dank fĂĽr Ihre Zeit, ich hoffe, ich habe es Ihnen aus einem bestimmten Grund genommen. Ich wĂĽrde mich ĂĽber Ihre Kommentare, konstruktive Kritik und Hilfe bei der Entwicklung des Projekts freuen. Aber was ist da - sogar ein Sternchen auf Github ist schon eine Belohnung.
Darüber hinaus möchte ich all jenen meinen Dank aussprechen, die mich unterstützt haben und in einem persönlichen, per E-Mail, in vk, Telegramm geschrieben haben. Ich hatte nach der Veröffentlichung des ersten Artikels keine solche Reaktion erwartet, es war eine angenehme Überraschung für mich und ein zusätzlicher Anreiz für die Entwicklung des Projekts. Danke!
Viele GrĂĽĂźe, Denis Ch.
"Der größte Teil meiner Arbeit ist die Qual der Geburt einer neuen wissenschaftlichen Disziplin" Benoit Mandelbrot