dy / spect

Observable selectors in DOM
https://dy.github.io/spect
MIT License
76 stars 8 forks source link

Drying up #85

Closed dy closed 5 years ago

dy commented 5 years ago

Spect is ok, but needs drying up / making some things right.

ghost commented 5 years ago

Hey dy,

Just want to shout out. Thank you for inspiration! I borrow a few ideas for my own lib.

🤟FTW, vuhg

dy commented 5 years ago

✌️ You're welcome. I saw pung, looks interesting. Although I chose implicit react-like subscriptions over explicit, like R.oberve/subscribe, and less technical things like registering. That was the subject of API design research.

ghost commented 5 years ago

I'm re-writing at the moment 😅. It will be something like this.

const app = make('div', (props, el) => {
  el.attr('class', 'container'); // static attributes
  el.prop('class', props.isDark && 'is-dark' ); // binding attribute or prop
  el.state('loading', true)

  R.when([el.didMount], () => {
    // loading or run anim
  })

  R.when([el.willMount], () => {
    // unsubscribe
  })

  el.watch(model) // trigger incremental-dom patch if model change

  el.html`some header code`;
  el.html`some main code`;
  el.html`some footer code`;

  // make.p`some text`.style('color', 'blue').style('padding', '16px')
  // there will be slots too
});

// static html, for static content, no binding or reactive
const staticButton = make.button`Hello World!`.css(``).render

render({
  where: '#app',
  app: app,
  props: { isDark: false },
  // router
  // store
})

Your spect remind me SwiftUI, which make me rethink and decide to rewrite. I'm watching your repo with great interest. Keep up the great work!

dy commented 5 years ago

Nice! I had these design considerations too, but opted for merging props & el

(props, el) => {...}  // → (props) => {...}

in favor of direct element props - el is props in native DOM, and custom elements literally assign props to el, so that would be also react-compatible

make('div', (el /* which is props */) => {
  el.attr('class', 'container'); // static attributes
  el.prop('class', props.isDark && 'is-dark' ); // binding attribute or prop
  el.state('loading', true)

But then to provide effects like attr, prop, ... you need some "wrapper", that was initially in your code as el. But jQuery pattern is perfect for that purpose: $el = $(el).

Also I like your symbols approach for identifying lifecycle events to trigger hooks:

R.when([el.didMount], fn)

I had these considerations in https://github.com/spectjs/spect/issues/66#issuecomment-524059032, even started implementing custom async behavior. But turned out that time is just a form of "domain" - usually we work with "spatial" domains, like DOM, storage etc. - but, by analogy stream is array distributed in time, - async behavior is just another "effect" for time domain (hope that makes any sense :)) To explain a bit better

el.attr('class', 'container'); // attr is "attributes" domain
el.prop('class', ...) // prop is "properties" domain

// there can also be any other domain
el.state() // element state domain
el.html() // html domain
el.class() // classList domain
el.css() // styles domain
el.data() // dataset domain
el.local() // local storage domain
el.remote() // remote storage domain, with async write etc.
el.repo() // fancy: github repository domain
...
el.on() // events domain (distributed in time)
el.raf() // animation frame domain, also distributed in time
el.connected() // lifecycle domain - distributed in time
// ↑ so that's logically the R.when([el.didMount])
ghost commented 5 years ago

While I like React Component composition idea, i don't like its hooks or states management. React's front-page states:A JavaScript library for building user interfaces. So i stay away from react-compatible idea.

So props, el idea is from Vue 3's design, props, context.

function make(tag, fn) {
 return (props) => {
   const el = View(tag)
   // other stuff
   fn(props, el)
   // other stuff
   return el
 }
}

const app = make('div', (props, el) => {})

app({ /* some props*/ })

Thinking about event in time easier than hook lifecycle. That is why i don't like on, connected, mounted. el.didMount is a signal, event (observable object) that allows you to subscribe.

I would refer to have local, repo should be in spect/utils or spect-utils. I would like to use:

import { raf } from 'spect'

raf(() => {
// schedule some animation
})

Your ideas are interesting, i will spend some time to read your issues and steal some. Hehehe. (I will mention you credit, of course.) I would love to chat with you about this if you have time. Discord: vuhg_3000

dy commented 5 years ago

Hm, can't find you in discord.

I very much like the spect-utils, usually wholesome packages are better than a bunch of plugins, in terms of usability/bugs/friction. As for global effects - the first design was just pure global effects. I liked it pretty much.

import $, { fx, html, state } from 'spect'

$('.target', el => {
  let { count=0 } = state()

  html`seconds: ${ count }`

  setTimeout(() => {
    state({ count: count++}) // the problem
  }, 1000)
})

The problem here comes in async call in setTimeout. The global state effect in no way can figure out what element that state belongs to. For that, async callstack support is required, which is not available by default in browsers. Besides, context-free effects create difficulty switching context within an aspect:

// this is canvas2D-like way to switch context for effects
$(target, el => {
  context(external)
  // ...effects

  context(el)
  // ...effects
})

That ↑ idea (#42) is problematic with async calls too.

So everything indicates (#86) for now that using Proxies for wrapped collections is the most livable solution.

ghost commented 5 years ago

Sorry, I'm vuhg_3000#1842 and my email is hi at v8dot1.me.

dy commented 5 years ago

8.0.0 is atomic, the matter of enhancing atomic effects.