Skip to content

Possible feature: mobx-like magic #2

Description

@43081j

Mobx heavily uses "magic" (i.e. hides away most of the reactions/observations by default and 'figures them out').

Not everyone is a fan of this but it has proven to be fairly popular, so i had a go at doing similar with atom.

Here's what i have:

Usage

export class MyElement extends AtomLitElement {
  render() {
    return html`
      <button @click="${() => setCount(old => old - 1)}">-</button>
      <span>${count.getState()}</span>
      <button @click="${() => setCount(old => old + 1)}">+</button>
    `;
  }
}

Implementation

import {LitElement, html, PropertyValues} from 'lit-element';

// NOTE: atoms isn't currently exported
import {atom, atoms} from "@klaxon/atom";

const [count, setCount] = atom({
  key: 'count',
  default: 0
});

// Truly excellent name
class AtomicReactor {
  public callback: () => void;
  protected _accessed: Set<string> = new Set();

  public constructor(callback: () => void) {
    this.callback = callback;
  }

  public track(): () => void {
    const originalGet = atoms.get;

    // NOTE: overwriting a method like this always feels like hackery
    // but the only other way i suppose is to have atom itself have a concept
    // of notifying of accesses (e.g. an event fired when one is accessed, but more
    // performant than that).
    atoms.get = (key: string) => {
      this._accessed.add(key);
      return originalGet.call(atoms, key);
    };

    return () => {
      atoms.get = originalGet;

      for (const [key, atom] of atoms.entries()) {
        if (this._accessed.has(key)) {
          // NOTE: needs to be removed at some point so we don't fill
          // memory with event listeners over time
          atom.addEventListener(key, () => {
            this.callback();
          });
        }
      }
    };
  }
}

const reactor = Symbol();

class AtomLitElement extends LitElement {
  protected [reactor]?: AtomicReactor;

  public connectedCallback(): void {
    super.connectedCallback();

    this[reactor] = new AtomicReactor(() => {
      this.requestUpdate();
    });
  }

  public disconnectedCallback(): void {
    super.disconnectedCallback();

    // NOTE: probably needs a this[reactor].dispose() which
    // removes all those previously mentioned listeners
    this[reactor] = undefined;
  }

  protected update(change: PropertyValues): void {
    const reactorInstance = this[reactor];
    if (reactorInstance) {
      const disposer = reactorInstance.track();
      super.update(change);
      disposer();
    } else {
      super.update(change);
    }
   }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions