Fractal schizophrenia. What`s up?







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"
      
      





CodeSandbox







, ? 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"}
      
      





CodeSandbox







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}
      
      





CodeSandbox







- , β€” 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}
      
      





CodeSandbox







? , , . 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"}
      
      





CodeSandbox







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'
      
      





CodeSandbox







β€”



, . .







const increment = mutator((i = -1) => i + 1)

const timer = cause(function* (ctx: Context) {
    // ...
    while (true) {
        // ...
        //    
        yield increment
    }
    // ...
})
      
      





CodeSandbox







β€” , . , . , 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>
*/
      
      





CodeSandbox









β€” 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)
      
      





CodeSandbox







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)
      
      





CodeSandbox









, , , :







  • yield*



    β€”
  • yield



    β€”
  • return



    β€”
  • throw



    β€”




β€” , whatsup



js-framework-benchmark. - , β€” , : , , , , , . . , whatsup



, inferno



, preact



, vue



, react



angular















, "" , . , , " " β€” .







-





3 kb gzip. β€” whatsup



. , 5-.







Glitch free



, . "". , ( Glitches).









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



.







cellx



(Riim β€” ).











, . , 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








All Articles