From 8e6e2de5a2af27832ea139a7b76fc63ae56cc1f1 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Thu, 12 Dec 2024 08:34:14 +0100 Subject: [PATCH] Change delay to double microtask for prop updates (#609) * Change delay to double microtask for prop updates * Add changest --- .changeset/tidy-rivers-fly.md | 5 ++ packages/preact/src/index.ts | 90 +++++++++++++++++++++++++++-------- 2 files changed, 74 insertions(+), 21 deletions(-) create mode 100644 .changeset/tidy-rivers-fly.md diff --git a/.changeset/tidy-rivers-fly.md b/.changeset/tidy-rivers-fly.md new file mode 100644 index 00000000..e0830e91 --- /dev/null +++ b/.changeset/tidy-rivers-fly.md @@ -0,0 +1,5 @@ +--- +"@preact/signals": patch +--- + +Change timing to a double microtask so we are behind the Preact render queue but can't delay as much as a user-input coming in. diff --git a/packages/preact/src/index.ts b/packages/preact/src/index.ts index 3a1b0fec..cc24fd51 100644 --- a/packages/preact/src/index.ts +++ b/packages/preact/src/index.ts @@ -79,21 +79,47 @@ function SignalValue(this: AugmentedComponent, { data }: { data: Signal }) { const currentSignal = useSignal(data); currentSignal.value = data; - const s = useComputed(() => { - let data = currentSignal.value; - let s = data.value; - return s === 0 ? 0 : s === true ? "" : s || ""; - }); + const s = useMemo(() => { + let self = this; + // mark the parent component as having computeds so it gets optimized + let v = this.__v; + while ((v = v.__!)) { + if (v.__c) { + v.__c._updateFlags |= HAS_COMPUTEDS; + break; + } + } - const isText = useComputed(() => { - return !isValidElement(s.peek()) || this.base?.nodeType === 3; - }); + const wrappedSignal = computed(function (this: Effect) { + let data = currentSignal.value; + let s = data.value; + return s === 0 ? 0 : s === true ? "" : s || ""; + }); - useSignalEffect(() => { - if (isText.value) { - (this.base as Text).data = data.peek(); - } - }); + const isText = computed( + () => isValidElement(wrappedSignal.value) || this.base?.nodeType !== 3 + ); + + this._updater!._callback = () => { + if (isValidElement(s.peek()) || this.base?.nodeType !== 3) { + this._updateFlags |= HAS_PENDING_UPDATE; + this.setState({}); + return; + } + (this.base as Text).data = s.peek(); + }; + + effect(function (this: Effect) { + if (!oldNotify) oldNotify = this._notify; + this._notify = notifyDomUpdates; + const val = wrappedSignal.value; + if (isText.value && self.base) { + (self.base as Text).data = val; + } + }); + + return wrappedSignal; + }, []); return s.value; } @@ -228,8 +254,8 @@ function createPropUpdater( props = newProps; }, _dispose: effect(function (this: Effect) { - if (!oldNotifyEffects) oldNotifyEffects = this._notify; - this._notify = notifyEffects; + if (!oldNotify) oldNotify = this._notify; + this._notify = notifyDomUpdates; const value = changeSignal.value.value; // If Preact just rendered this value, don't render it again: if (props[prop] === value) return; @@ -349,26 +375,48 @@ export function useComputed(compute: () => T) { return useMemo(() => computed(() => $compute.current()), []); } -let oldNotifyEffects: (this: Effect) => void, - effectsQueue: Array = []; +let oldNotify: (this: Effect) => void, + effectsQueue: Array = [], + domQueue: Array = []; -const defer = +const deferEffects = typeof requestAnimationFrame === "undefined" ? setTimeout : requestAnimationFrame; +const deferDomUpdates = (cb: any) => { + queueMicrotask(() => { + queueMicrotask(cb); + }); +}; + function flushEffects() { batch(() => { let inst: Effect | undefined; while ((inst = effectsQueue.shift())) { - oldNotifyEffects.call(inst); + oldNotify.call(inst); } }); } function notifyEffects(this: Effect) { if (effectsQueue.push(this) === 1) { - (options.requestAnimationFrame || defer)(flushEffects); + (options.requestAnimationFrame || deferEffects)(flushEffects); + } +} + +function flushDomUpdates() { + batch(() => { + let inst: Effect | undefined; + while ((inst = domQueue.shift())) { + oldNotify.call(inst); + } + }); +} + +function notifyDomUpdates(this: Effect) { + if (domQueue.push(this) === 1) { + (options.requestAnimationFrame || deferDomUpdates)(flushDomUpdates); } } @@ -378,7 +426,7 @@ export function useSignalEffect(cb: () => void | (() => void)) { useEffect(() => { return effect(function (this: Effect) { - if (!oldNotifyEffects) oldNotifyEffects = this._notify; + if (!oldNotify) oldNotify = this._notify; this._notify = notifyEffects; return callback.current(); });