luisherranz / deepsignal

DeepSignal 🧶 - Preact signals, but using regular JavaScript objects

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Feature request: computed

rcoopr opened this issue · comments

I like the DX of using the signal.value by default and accessing the underlying signal with the $ prefix, but context switching when using computed values is awkward. It would be nice to provide computed with the same functionality as Preact's computed, only returning a proxy object instead for the improved DX

I'm not sure what you mean. Could you please explain it again using an example?

Currently, we have

import { computed, effect } from '@preact/signals-core';
import { deepSignal } from 'deepsignal/core';

const deep = deepSignal({
  a: 5,
  b: 10
})

const multiplied = computed(() => ({
  a: deep.a * 2,
  b: deep.b * 2,
}))

effect(() => {
  // some effect using a combination of deepSignals directly, and computed values
  console.log(deep.a, multiplied.value.a)
  // ^ above, we can use `deep.a` but not `multiplied.a`
})
// proposed:
effect(() => {
  console.log(deep.a, multiplied.a)
  // accessing `multiplied.a` would return the value
  // after all, `computed`s are also signals
})

Some care might need to be given to the case where a computed returns a primitive, so the current way of accessing the signal through obj.$prop wouldn't work the same

i.e:

const primitive = computed(() => deep.a * 2)
// `primitive` would return a value, but where is the signal? `primitive.$`?

Is there something wrong with getters? They use computed underneath and they'll give you the same $ API as regular signals:

import { effect } from "@preact/signals-core";
import { deepSignal } from "deepsignal/core";

const deep = deepSignal({
  a: 5,
  b: 10,
});

const multiplied = deepSignal({
  get a() {
    return deep.a * 2;
  },
  get b() {
    return deep.b * 2;
  },
});

effect(() => {
  // this works fine now
  console.log(deep.a, multiplied.a);
});

You can also use them in the same object:

import { effect } from "@preact/signals-core";
import { deepSignal } from "deepsignal/core";

const deep = deepSignal({
  a: 5,
  b: 10,
  get doubleA() {
    return deep.a * 2;
  },
  get doubleB() {
    return deep.b * 2;
  },
});

Docs: https://github.com/luisherranz/deepsignal#get-prop---

Sorry, I glossed over that in the docs. I suppose the case where a computed returns a primitive is sort of outside the scope for this module dealing with objects

I suppose the case where a computed returns a primitive is sort of outside the scope for this module dealing with objects

Do you mean this?

const primitive = computed(() => deep.a * 2)
// `primitive` would return a value, but where is the signal? `primitive.$`?

Yeah

Then, yeah. If you just have a primitive value, you can't know when it's being written or read. The only exception is Svelte components because they are compiled.

// Solid read
count();
// Solid write
setCount("hi");

// Preact / Vue read
count.value;
// Preact / Vue write
count.value = "hi";

// DeepSignal read
state.count;
// DeepSignal write
state.count = "hi";

// Svelte read
count;
// Svelte write
count = "hi";
// Svelte (final code after compilation)
$$invalidate(0, count = "hi");

Closing now but feel free to reopen if there's anything else that you want to discuss.