通过Ajax与StimulusJS提交表单时,如何同时响应json和turbo_stream?
问题解答
核心原因
Rails 的 respond_to 是根据请求的 Accept 头返回对应格式的响应,一次 HTTP 请求只能返回一种响应格式。同时,render 方法会立即终止响应流程,所以你代码中写的第二个 render 永远不会执行,只会返回第一个定义的格式。
实现需求的可行方案
要同时获取 JSON 数据和 Turbo Stream 的页面更新效果,需要通过变通方式实现,以下是两种常用方案:
方案1:返回 Turbo Stream + 前端事件传递 JSON 数据(推荐)
这种方案只需一次请求,通过在 Turbo Stream 模板中嵌入自定义事件,将 JSON 数据传递给前端处理。
- 调整控制器代码(保留两种格式作为备选,实际请求只返回 Turbo Stream):
respond_to do |format| if current_model.valid? format.turbo_stream { render :success, locals: { data: data } } format.json { render json: { data: data, status: 200 } } else format.turbo_stream { render :errors, locals: { data: data } } format.json { render json: { data: data, status: 500 } } end end
- 修改 Turbo Stream 模板(比如
success.turbo_stream.erb):
# 先处理页面更新逻辑 <%= turbo_stream.update 'target_element_id', partial: 'success_content' %> # 嵌入JS发送自定义事件,携带JSON数据 <script> document.dispatchEvent(new CustomEvent('form:success', { detail: <%= data.to_json.html_safe %> })); </script>
- 在 Stimulus 控制器中监听事件并处理 JSON 数据:
// 你的Stimulus控制器 export default class extends Controller { connect() { document.addEventListener('form:success', this.handleSuccess.bind(this)); document.addEventListener('form:error', this.handleError.bind(this)); } handleSuccess(event) { console.log('获取的JSON数据:', event.detail); // 这里可以处理JSON数据的业务逻辑 } handleError(event) { console.error('错误数据:', event.detail); } }
方案2:发起两次请求(先拿JSON,再请求Turbo Stream)
这种方案会发起两次请求,适合必须先处理 JSON 再更新页面的场景:
// 用Stimulus的方式提交(替代jQuery Ajax) submitForm(event) { event.preventDefault(); const form = event.target; const formData = new FormData(form); // 第一步:请求JSON格式 fetch(form.action, { method: form.method, body: formData, headers: { 'Accept': 'application/json' } }) .then(res => res.json()) .then(jsonData => { console.log('JSON数据:', jsonData); // 第二步:请求Turbo Stream格式更新页面 return fetch(form.action, { method: form.method, body: formData, headers: { 'Accept': 'text/vnd.turbo-stream.html', 'X-CSRF-Token': Rails.csrfToken() } }); }) .then(res => res.text()) .then(turboHtml => Turbo.renderStreamMessage(turboHtml)) .catch(err => console.error('请求失败:', err)); }
总结
HTTP 协议的限制决定了一次请求无法返回两种格式,推荐使用方案1,它更符合 Rails + Turbo + Stimulus 的生态设计,且性能更优。
内容的提问来源于stack exchange,提问作者Victor Luna
相关产品推荐
相关产品推荐

