Rails6升级Rails7后Action Cable频道无法监听问题求助
Rails 7升级后Action Cable频道无法监听消息问题修复
问题现象
项目从Rails 6升级至Rails 7后,原有Action Cable频道不再正常接收、监听消息,原Rails 6搭配Turbolinks环境下功能可正常运行。
涉及的原有配置代码如下:
app/javascript/channels/consumer.js
import { createConsumer } from "@rails/actioncable" export default createConsumer()
app/javascript/channels/settings_channel.js
import consumer from "./consumer" consumer.subscriptions.create({ channel: "SettingsChannel" }) document.addEventListener('turbo:load', () => { //console.log(attendance_id); consumer.subscriptions.create({ channel: "SettingsChannel"}, { connected() { console.log("connected to settings channel "); }, disconnected() { // Called when the subscription has been terminated by the server }, received(data) { console.log("settings channel data:"); console.log(data); if(data.tab){ //clear other selected tabs first then highlight selected ones. const actions_tab = document.getElementById('actions-tab'); if(actions_tab){ actions_tab.classList.remove('settings-active-tab','settings-inactive-tab'); if(data.tab == 'actions'){ actions_tab.classList.add('settings-active-tab'); }else{ actions_tab.classList.add('settings-inactive-tab'); } } const committees_tab = document.getElementById('committees-tab'); if(committees_tab){ committees_tab.classList.remove('settings-active-tab','settings-inactive-tab'); if(data.tab == 'committees'){ committees_tab.classList.add('settings-active-tab'); }else{ committees_tab.classList.add('settings-inactive-tab'); } } const members_tab = document.getElementById('members-tab'); if(members_tab){ members_tab.classList.remove('settings-active-tab','settings-inactive-tab'); if(data.tab == 'members'){ members_tab.classList.add('settings-active-tab'); }else{ members_tab.classList.add('settings-inactive-tab'); } } const motions_tab = document.getElementById('motions-tab'); if(motions_tab){ motions_tab.classList.remove('settings-active-tab','settings-inactive-tab'); if(data.tab == 'motions'){ motions_tab.classList.add('settings-active-tab'); }else{ motions_tab.classList.add('settings-inactive-tab'); } } const session_tab = document.getElementById('session-tab'); if(session_tab){ session_tab.classList.remove('settings-active-tab','settings-inactive-tab'); if(data.tab == 'session'){ session_tab.classList.add('settings-active-tab'); }else{ session_tab.classList.add('settings-inactive-tab'); } } const system_tab = document.getElementById('system-tab'); if(system_tab){ system_tab.classList.remove('settings-active-tab','settings-inactive-tab'); if(data.tab == 'system'){ system_tab.classList.add('settings-active-tab'); }else{ system_tab.classList.add('settings-inactive-tab'); } } } //end of inserted code for data received } }); })
app/javascript/application.js
import "@hotwired/turbo-rails" //below is not supported yet //import "@hotwired/stimulus-importmap-autoloader" import "./controllers" import "@rails/actioncable" import "@rails/activestorage" import "./channels" import Rails from "@rails/ujs" window.Rails = Rails; if(Rails.fire(document, "rails:attachBindings")){ Rails.start(); }
config/importmap.rb
# Pin npm packages by running ./bin/importmap pin "application", preload: true pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true #pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true pin_all_from "app/javascript/controllers", under: "controllers" # Use libraries available via the asset pipeline (locally or via gems). # Rails 7.0 required pin "@rails/actioncable", to: "actioncable.esm.js" pin "@rails/activestorage", to: "activestorage.esm.js"
问题根因
原有配置存在4个核心问题,和Rails 7替换Turbolinks为Turbo的生命周期变更、模块加载逻辑冲突直接相关:
- 顶层创建了无任何业务回调的空
SettingsChannel订阅,这个订阅没有received等处理逻辑,属于无效订阅,会占用连接资源干扰正常订阅逻辑。 - 带业务回调的有效订阅被放在
turbo:load事件内创建,Turbo驱动的无刷新导航下,每次页面访问都会触发turbo:load,会重复生成大量重复订阅,同时Turbo缓存机制不会自动销毁旧订阅,会导致连接状态混乱,消息无法路由到正确的回调函数。 application.js中重复引入@rails/actioncable模块,和./channels入口加载的actioncable实例冲突,会导致连接重复初始化、状态异常。- 没有处理Turbo页面缓存恢复的场景,从缓存恢复页面时原有WS连接可能已断开,不会自动重连。
修复步骤
1. 修正频道订阅逻辑
重写app/javascript/channels/settings_channel.js,移除空订阅、将有效订阅提升到顶层只初始化一次,增加Turbo渲染时的连接检查,同时简化重复的tab切换逻辑:
import consumer from "./consumer" // 仅在模块加载时创建一次带完整回调的订阅 consumer.subscriptions.create({ channel: "SettingsChannel"}, { connected() { console.log("connected to settings channel "); }, disconnected() { // 订阅被服务端终止时触发 }, received(data) { console.log("settings channel data:", data); if(data.tab){ // 统一处理所有tab的状态切换,消除重复代码 const tabList = ['actions', 'committees', 'members', 'motions', 'session', 'system'] tabList.forEach(tabName => { const tabEl = document.getElementById(`${tabName}-tab`) if(!tabEl) return tabEl.classList.remove('settings-active-tab','settings-inactive-tab') tabEl.classList.add(tabName === data.tab ? 'settings-active-tab' : 'settings-inactive-tab') }) } } }); // Turbo渲染页面时检查连接状态,断开则自动重连 document.addEventListener('turbo:render', () => { if(!consumer.connection.isOpen()) consumer.connection.open() })
2. 修正入口文件加载逻辑
修改app/javascript/application.js,移除重复的模块引入,修正Rails UJS和ActiveStorage的启动逻辑:
import "@hotwired/turbo-rails" import "./controllers" import "./channels" import * as ActiveStorage from "@rails/activestorage" ActiveStorage.start() import Rails from "@rails/ujs" window.Rails = Rails Rails.start()
3. 确认频道入口文件存在
检查app/javascript/channels/index.js是否存在,内容如下,确保所有_channel.js结尾的文件会被自动加载:
const channels = require.context('.', true, /_channel\.js$/) channels.keys().forEach(channels)
额外排查项
如果修改后仍有问题,逐一检查以下配置:
- 确认布局文件
app/views/layouts/application.html.erb的<head>标签内存在<%= action_cable_meta_tag %>,否则Action Cable无法获取正确的WS连接地址。 - 检查
config/cable.yml配置,开发环境可使用默认的async适配器,生产环境必须使用Redis等支持跨进程广播的适配器,不能使用async。 - 打开浏览器开发者工具的「网络-WS」面板,检查Action Cable连接状态是否为101 Switching Protocols,是否成功发送了
SettingsChannel的订阅指令,服务端是否有对应的订阅日志输出。
内容的提问来源于stack exchange,提问作者epurdy
相关产品推荐
相关产品推荐

