You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Solid-js中更新消息后如何等待Div重绘再执行滚动?

SolidJS 更新消息后滚动到底部的最优方案

问题描述

我编写了一段SolidJS代码,需求是监听topic变化时,获取新消息后让容器重绘最新内容,再滚动到底部。但setMessages(chat)并未立即触发DOM重绘,导致滚动代码执行时机过早,滚动位置不准确。尝试用createRenderEffect实现更新消息和滚动,但消息无法正确更新。目前有两个临时方案:

  • 将滚动代码放入0ms的setTimeout中
  • 用async函数包装消息更新并await后执行滚动
    想请教是否有更优的解决方案?

代码示例

import { For, createEffect, on, type VoidComponent } from "solid-js";
import { createSignal } from "solid-js";
import { createStore } from "solid-js/store";

function getRandomInt(min: number, max: number) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function getTopicChat() {
  const n = getRandomInt(12, 24);
  return Array.from(Array(n).keys());
}

const Home: VoidComponent = () => {
  let containerDiv!: HTMLDivElement;

  const [topic, setTopic] = createSignal("TOPIC 1");
  const [messages, setMessages] = createStore<number[]>([]);

  createEffect(
    on(topic, async () => {
      const chat = getTopicChat();
      setMessages(chat);
      containerDiv.scroll({ top: containerDiv.scrollHeight });
    })
  );

  return (
    <main class="h-screen">
      <div class="flex justify-center">
        <button
          onClick={() => setTopic("Topic " + getRandomInt(2, 99).toString())}
          class="bg-red-100 py-2 px-4 rounded-md my-2"
        >
          Change topic
        </button>
      </div>

      <div class="text-center">
        <p>{topic()}</p>
      </div>

      <div class="mx-20 overflow-y-auto h-96" ref={containerDiv}>
        <For each={messages}>
          {(i) => <div class="bg-green-200 h-40 my-4">{i}</div>}
        </For>
      </div>
    </main>
  );
};

export default Home;

尝试过的createRenderEffect代码

createRenderEffect(
  on(topic, () => {
    const chat = getTopicChat();
    setMessages(chat);
    if (containerDiv) {
      containerDiv.scroll({ top: containerDiv.scrollHeight });
    }
  })
);

最优解决方案

核心是利用SolidJS的响应式机制,在DOM更新完成后再执行滚动操作。可以通过单独监听messages的变化来实现,因为createEffect会在DOM渲染完成后触发:

方案代码

import { For, createEffect, on, type VoidComponent } from "solid-js";
import { createSignal } from "solid-js";
import { createStore } from "solid-js/store";

function getRandomInt(min: number, max: number) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function getTopicChat() {
  const n = getRandomInt(12, 24);
  return Array.from(Array(n).keys());
}

const Home: VoidComponent = () => {
  let containerDiv!: HTMLDivElement;

  const [topic, setTopic] = createSignal("TOPIC 1");
  const [messages, setMessages] = createStore<number[]>([]);

  // 监听topic变化,仅负责更新消息
  createEffect(
    on(topic, () => {
      const chat = getTopicChat();
      setMessages(chat);
    })
  );

  // 监听messages变化,DOM更新后执行滚动
  createEffect(() => {
    // 读取messages建立依赖,确保DOM已完成更新
    messages;
    containerDiv.scroll({ top: containerDiv.scrollHeight });
  });

  return (
    <main class="h-screen">
      <div class="flex justify-center">
        <button
          onClick={() => setTopic("Topic " + getRandomInt(2, 99).toString())}
          class="bg-red-100 py-2 px-4 rounded-md my-2"
        >
          Change topic
        </button>
      </div>

      <div class="text-center">
        <p>{topic()}</p>
      </div>

      <div class="mx-20 overflow-y-auto h-96" ref={containerDiv}>
        <For each={messages}>
          {(i) => <div class="bg-green-200 h-40 my-4">{i}</div>}
        </For>
      </div>
    </main>
  );
};

export default Home;

方案说明

  1. 拆分逻辑:将消息更新和滚动操作分离,topic变化时仅处理消息更新逻辑
  2. 依赖messages的createEffect会在DOM渲染完成后触发,此时容器的scrollHeight已经对应新消息的高度,滚动位置准确
  3. 完全利用SolidJS的响应式系统,避免了定时器或异步包装带来的不确定性,代码更贴合框架设计规范

内容的提问来源于stack exchange,提问作者Max

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 16:33:16