Skip to content

Instantly share code, notes, and snippets.

@timhall
Last active November 28, 2018 22:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timhall/81b599cdce819a1599298825c83d40af to your computer and use it in GitHub Desktop.
Save timhall/81b599cdce819a1599298825c83d40af to your computer and use it in GitHub Desktop.
Pull-based reactive declarations

Reactive Declarations

To avoid issues with re-calculating immediately on change while keeping reactive values in-sync, use reactive accessors to implement pull-based computed values. Propose using computed for pull-based computed values that are kept synchronized and reactive to run functionality on initialize and before beforeUpdate.

Option 2 uses a single reactive: label and can re-run blocks as-needed based on when reactive values are accessed. I think there are a few downsides to this (side effects could happen at strange times), but it's a bit simpler since it presents a single reactive API.

// Initialized with <Component a=15 />

export let a = 10;
let b = 20;

assert.equal(a, 15);

let c, d;
computed: c = a + 1;
computed: d = expensiveSum(a, b);
reactive: console.log(c); // -> 16 (on initialize)

assert.equal(d, 35);

function handle_click() {
  a += 1;
  assert.equal(c, 17);

  b += 1;
  assert.equal(d, 37);
}

Transforms roughly into:

function init($$self, $$initial_props, $$make_dirty, $$make_reactive) {
  const __recompute = { flag: 1 | 2 | 4 };
  const __dirty_a = () => { $$make_dirty('a'); __recompute.flag |= 1 | 2; };
  const __dirty_b = () => { $$make_dirty('b'); __recompute.flag |= 2; };
  const __dirty_c = () => { $$make_dirty('c'); __recompute.flag |= 4; };

  let { a = 10 } = $$initial_props;
  let b = 20;

  assert.equal(a, 15);

  let c, d;
  const __c = $$make_reactive(__recompute, 1, () => { c = a + 1; __dirty_c(); });
  const __d = $$make_reactive(__recompute, 2, () => { d = expensiveSum(a, b); $$make_dirty('d'); });
  const __auto_0 = $$make_reactive(__recompute, 4, () => { console.log(c); }; __auto_0();

  __c();
  assert.equal(c, 16);
  __d();
  assert.equal(d, 35);

  function handle_click() {
    a += 1;
    __dirty_a();
    __c();
    assert.equal(c, 17);
    
    b += 1;
    __dirty_b();
    __d();
    assert.equal(d, 37);
  }

  $$self.get = () => {
    __c(); __d();
    return { a, b, c, d };
  };

  $$self.synchronize = $$dirty => {
    __auto_0();
  }
}

Where $$make_reactive is roughly:

function $$make_reactive(recompute, id, compute) {
  return () => {
    if (recompute.flag & id) {
      compute();
      recompute.flag ^= id;
    }
  };
}

The doubled-quadrupled example from RFC#8 would look like:

function init($$self, $$initial_props, $$make_dirty, $$make_reactive) {
  const __recompute = { flag: 3 };
  const __dirty_count = () => { $$make_dirty('count'); __recompute.flag |= 1; };
  const __dirty_doubled = () => { $$make_dirty('doubled'); __recompute.flag |= 2; };
  
  let count = 1;
  let doubled;
  let quadrupled;

  __doubled = $$make_reactive(__recompute, 1, () => { doubled = count * 2; __dirty_doubled() });
  __quadrupled = $$make_reactive(__recompute, 2, () => { quadrupled = doubled * 2; $$make_dirty('quadrupled') });

  function handle_click() {
    count += 1;
    __dirty_count();
  }

  $$self.get = () => {
    __doubled(); __quadrupled();
    return { count, doubled, quadrupled, handle_click }
  };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment