SolidJS:从 IntersectionObserver 回调中更新信号

SolidJS: Updating signal from within IntersectionObserver Callback

以下是 solid.js 应用程序的代码片段。我正在尝试从 IntersectionObserver 的回调中更新顶级信号的状态。状态在回调范围内更新,但不在回调范围外。

问题一:这怎么可能?如果我可以访问 setIds() 然后操纵该值 - 什么会阻止此值更新发生在回调范围之外?

问题二:有人知道如何从回调中更新信号状态吗?

const [ids, setIds] = createSignal(new Set<string>());

createEffect(() => {
    const callback = (entries: any, observer: any) => {
        const current = ids()
        entries.forEach((e: any) => {
            const intersecting = e.isIntersecting;
            const id = e.target.id;
            if (intersecting) {
                setIds(current.add(id));
            }
            else {
                setIds(current.remove(id));
            }
        });
        // NOTE: The following outputs the correct value
        console.log("This is the correct value:", ids())
    };
    const observer = new IntersectionObserver(callback);
    const elements = Array.from(document.querySelectorAll("p"));
    elements.forEach((element) => observer.observe(element));
    return () => observer.disconnect();
}, [setIds]);

// NOTE: The following does NOT update at all.
createEffect(() => console.log("This value is not being updated:", ids()));

以下内容适用于喜欢在真实场景中进行测试的人:

我还提供了一个包含整个 solid.js 应用程序的代码块,可用于测试。该应用程序在一个最大高度为 ${n} 像素的自动滚动元素中提供了一个包含 1,000 个段落标记的循环 - 创建滚动效果。每个段落都带有一个(n?)个唯一 ID。 我的 objective 有一个不断更新的列表(存储在顶级信号中),我可以用它来确定哪些 ID 是可见的。 到目前为止,我一直尝试使用包含 callbackIntersectionObserver 来做到这一点。这在 callback 之内工作,但是, callback 函数的外部 未更新该值。

import {
    For,
    createSignal,
    createEffect,
} from "solid-js";
import { render } from 'solid-js/web';

const App = () => {

    const [ids, setIds] = createSignal(new Set<string>());

    createEffect(() => {
        const callback = (entries: any, observer: any) => {
            const current = ids()
            entries.forEach((e: any) => {
                const intersecting = e.isIntersecting;
                const id = e.target.id;
                if (intersecting) {
                    setIds(current.add(id));
                }
                else {
                    setIds(current.remove(id));
                }
            });
            // NOTE: The following outputs the correct value
            console.log("This is the correct value:", ids())
        };
        let options = {
            root: document.getElementById("main"),
            threshold: 1.0
        }
        const observer = new IntersectionObserver(callback, options);
        const elements = Array.from(document.querySelectorAll("p"));
        elements.forEach((element) => observer.observe(element));
        return () => observer.disconnect();
    }, [setIds]);

    // NOTE: The following does NOT update at all.
    createEffect(() => console.log("This value is not being updated:", ids()));

    const arr = Array.from(Array(1000).keys())
    return (
        <div id="main" class="overflow-auto" style="max-height: 550px;">
            <For each={arr}>
                {(number) => {
                    return (
                        <p id={`${number}`}>
                            {number}
                        </p>
                    )
                }}
            </For>
        </div>
    );
};

render(() => <App />, document.getElementById('root') as HTMLElement);

信号不关心它们在什么范围内,影响与否,所以在交叉路口观察者中是无关紧要的。

createSignal 返回的 setter 函数对上一个值和下一个值运行身份检查以避免不必要的更新。

在您的示例中,您没有设置新值,而是将其分配给一个变量,对其进行变异并重新设置。由于Sets是参考值,prev和next值指向同一个对象,ids() === current returns true,所以信号更新失败。

const [ids, setIds] = createSignal(new Set<string>());

 createEffect(() => {
   const current = ids();
   setIds(current); // Not a new value
 });

您需要传递一个新的 Set 来触发更新:

const [ids, setIds] = createSignal<Set<string>>(new Set<string>());

createEffect(() => {
  console.log(ids());
});

let i = 0;
setInterval(() => {
  setIds(new Set([...ids(), String(i++)]));
}, 1000);

或者您可以通过传递 { equals: false } 强制 re-running 效果,即使没有触发更新:

const [ids, setIds] = createSignal(new Set<string>(), { equals: false });

现在将始终调用效果:

createEffect(() => {
  console.log(ids());
});

let i = 0;
setInterval(() => {
  ids().add(String(i++));
  setIds(ids()); // Not a new value
}, 1000);