如何判断Turbo.renderStreamMessage执行完成并操作新增DOM元素?
解决方案
你遇到的问题核心是:Turbo.renderStreamMessage会同步更新DOM,但Stimulus并不会立刻扫描新添加的元素来更新目标集合(比如hasItemTarget),这个扫描过程是异步的,所以立即检查会返回false。下面是几种替代delay()的可靠方法:
方法1:利用Stimulus子控制器的connect事件(推荐)
这是最符合Stimulus设计理念的方式,把单个item的初始化逻辑封装到子控制器中,当元素被添加到DOM并完成Stimulus绑定后自动触发操作:
- 修改Turbo Stream模板,给新增的
<li>添加子控制器:
# myaction.turbo_stream.erb <%= turbo_stream.append "results" do %> <li data-controller="item" data-mystimulus-target="item">HELLO WORLD</li> <% end %>
- 创建子控制器
item_controller.js:
// item_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { connect() { // 元素已成功添加到DOM并完成Stimulus绑定 // 可以直接在这里执行item相关的操作,或者触发事件通知父控制器 this.element.dispatchEvent(new CustomEvent('item-ready', { bubbles: true })) } }
- 在父控制器中监听自定义事件:
// mystimulus_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["item"] connect() { // 监听item-ready事件 this.element.addEventListener('item-ready', (event) => { console.log("item已准备好", event.target) // 这里可以和目标元素交互 console.log("hasItemTarget:", this.hasItemTarget) // 此时返回true }) fetch(this.myRequest()) .then(response => { if (response.ok) return response.text() throw response.status }) .then(html => Turbo.renderStreamMessage(html)) .catch(error => console.warn(error)) } myRequest() { return new Request("myurl", { method: "GET", headers: { Accept: "text/vnd.turbo-stream.html" }, redirect: "manual" }); } }
方法2:使用MutationObserver监听DOM变化
通过观察者直接监听目标容器的子节点变化,当新元素添加后立即处理:
// mystimulus_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["item"] connect() { const resultsContainer = document.getElementById('results') // 创建观察者实例 this.observer = new MutationObserver((mutations) => { mutations.forEach(mutation => { if (mutation.type === 'childList' && mutation.addedNodes.length) { mutation.addedNodes.forEach(node => { if (node.matches('[data-mystimulus-target="item"]')) { // 确保Stimulus绑定新元素 this.application.connectElement(node) // 现在可以交互了 console.log("item已准备好", node) console.log("hasItemTarget:", this.hasItemTarget) } }) } }) }) // 开始监听容器的子节点变化 this.observer.observe(resultsContainer, { childList: true }) fetch(this.myRequest()) .then(response => { if (response.ok) return response.text() throw response.status }) .then(html => Turbo.renderStreamMessage(html)) .catch(error => console.warn(error)) } // 控制器销毁时断开观察者,避免内存泄漏 disconnect() { if (this.observer) this.observer.disconnect() } myRequest() { return new Request("myurl", { method: "GET", headers: { Accept: "text/vnd.turbo-stream.html" }, redirect: "manual" }); } }
方法3:监听Turbo Stream完成事件
Turbo在处理完Stream消息后会触发turbo:stream:completed事件,结合requestAnimationFrame确保DOM更新和Stimulus扫描完成:
// mystimulus_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["item"] connect() { // 只监听一次当前请求对应的Stream完成事件 const handleStreamComplete = () => { // 使用requestAnimationFrame确保Stimulus完成目标扫描 requestAnimationFrame(() => { console.log("hasItemTarget:", this.hasItemTarget) // 返回true // 执行后续操作 }) document.removeEventListener('turbo:stream:completed', handleStreamComplete) } document.addEventListener('turbo:stream:completed', handleStreamComplete) fetch(this.myRequest()) .then(response => { if (response.ok) return response.text() throw response.status }) .then(html => Turbo.renderStreamMessage(html)) .catch(error => console.warn(error)) } myRequest() { return new Request("myurl", { method: "GET", headers: { Accept: "text/vnd.turbo-stream.html" }, redirect: "manual" }); } }
内容的提问来源于stack exchange,提问作者Andy Harvey
相关产品推荐
相关产品推荐

