WebReflection / hyperHTML

A Fast & Light Virtual DOM Alternative
ISC License
3.07k stars 112 forks source link

[question] Late binding, and use with icomponent #290

Closed prasannavl closed 5 years ago

prasannavl commented 5 years ago

Hi, I'm currently writing a render agnostic component here: https://github.com/prasannavl/icomponent

The idea is that it provides lifecycle and explicit render control, while abstracting the rendering methods. I've been meaning to provide some hyperhtml examples, but have not been able to land on a satisfactory one yet. Currently I can use it with hyperhtml, with this:

import { IComponent } from "icomponent/lib";
import { wire, bind } from "hyperhtml";

class App extends IComponent {
  constructor() {
    super();
    this.html = bind(this.getRenderRoot());
  }

  _render() {}

  view() {
    return this.html`
          <section>
            Hello
          </section>
        `;
  }
}

But I'm not entirely happy with this, since it binds the renderRoot early, and also doesn't keep the view function a pure one, with an empty render.

For instance, here's an example with lithtml:

import { html, render } from "lit-html";
import { IComponent } from "icomponent";

class App extends IComponent {
 _render() {
      render(this.view(), this.getRenderRoot());
 }

  view() {
        return html`
        <div>Hello world!</div>
        `;
    }
}

customElements.define("x-app", App);

Here view is a pure fn, and render does the work, is the model I'm trying to provide for all renderers.

Most of the examples in my README also uses lit-html currently (by setting the render fn globally via IConfig.render). I'd love to provide some examples with hyperhtml instead. Eventually my goal is to encapsulate it into it's own HyperComponent, and provide them as a separate package. So, you can use mix and match all sorts of components on one application retaining the same render agnostic model. I was hoping you can point me in the right direction to accomplish this the best way for hyperhtml.

Thanks! :)

WebReflection commented 5 years ago

so, with hyperHTML you have no external dependencies but you call a pure function the one with external dependencies and also external logic via mandatory render?

Not sure I am following.

Anyway, would this option work ?

class App extends IComponent {
  get html() {
    return this._html || (this._html = bind(this.getRenderRoot()));
  }
  _render() {}
  view() {
    return this.html`
      <div>Hello world!</div>
    `;
  }
}

Or you can simply use hyperHTML.Component which works out of the box. 👋

WebReflection commented 5 years ago

P.S. what I meant, is that the moment you need a _render with a proper context, your purity in view() becomes meaningless

prasannavl commented 5 years ago

@WebReflection - By pure function, I mean a function without any side effects. What you just wrote is similar to the one the I had used (except you moved it to a property) :)

So, basically, view can do whatever, just no have side effects or do the actual rendering in the DOM. The idea is to provide the representation, the effects of which happen in render.

Or, simply put, calling view multiple times, should have no impact. But here, calling view DOES the actual render - which is what I'm hoping to avoid.

Basically, I was hoping I can get some pointers from you on the best way to separate view from render, and get a representation from the view function and perform the actual rendering on _render.

WebReflection commented 5 years ago

what about this?

import { IComponent } from "icomponent/lib";
import { bind } from "hyperhtml";
const html = (...args) => args;

class App extends IComponent {
  _render() {
    bind(this.getRenderRoot())(...this.view());
  }
  view() {
    return html`
      <div>Hello world!</div>
    `;
  }
}

hyperHTML is based on functions itself, you can play around as much as you like with those.

prasannavl commented 5 years ago

@WebReflection, That's neat. Thanks a lot for the help!

I ended up using the following impl here: https://github.com/prasannavl/icomponent/blob/master/packages/icomponent-hyper/lib/index.js

    constructor() {
        super();
        this._hyperRoot = null;
    }

    _render() {
        if (this._hyperRoot === null) {
            this._hyperRoot = bind(this.getRenderRoot());
        }
        this._hyperRoot(...this.view());
    }

It's all good to go, published, and works like charm: icomponent-hyper .. Just have to update the docs with hyperhtml examples that work out of the box :)

prasannavl commented 5 years ago

Ah, I thought bind was some expensive function that's used to bind the DOM, but looks like it's just a simple javascript bind. Reverting to your code, as the above provides no gain whatsoever.

WebReflection commented 5 years ago

Yup, bind is pretty much, exactly, the function prototype bind 😉

prasannavl commented 5 years ago

All done! Thanks!