Stimulus控制器点击时动作触发两次问题求助
问题:Stimulus控制器点击label时动作触发两次,点击input正常
Stimulus控制器仅加载一次,但点击label元素时,markAsChecked动作会触发两次;点击内部的input元素时则正常触发一次,需排查原因并解决。
相关代码
app/javascript/application.js
import "@hotwired/turbo-rails" import "controllers"
app/javascript/controllers/application.js
import { Application } from "@hotwired/stimulus" const application = Application.start() application.debug = false window.Stimulus = application export { application }
app/javascript/controllers/index.js
import { application } from "controllers/application" import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" eagerLoadControllersFrom("controllers", application)
app/javascript/controllers/radio_button_controller.js
import { Controller } from "@hotwired/stimulus" export default class extends Controller { static classes = [ 'active', 'inactive', 'invisible' ] connect() { console.log('connected to ', this.element.querySelector('input').value) } markAsChecked(event) { event.stopPropagation(); console.log('-', this.element.querySelector('input').value); } }
view.html.erb
<div class="flex flex-1 gap-x-8 justify-start"> <label data-controller="radio-button" data-action="click->radio-button#markAsChecked:stop"> <input type="radio" value="public" name="destination[access_type]" id="destination_access_type_public"> </label> <label data-controller="radio-button" data-action="click->radio-button#markAsChecked:stop"> <input type="radio" value="private" name="destination[access_type]" id="destination_access_type_private"> </label> <label data-controller="radio-button" data-action="click->radio-button#markAsChecked:stop"> <input type="radio" value="backend" name="destination[access_type]" id="destination_access_type_backend"> </label> </div>
开发者控制台测试结果
// 测试label点击 temp1 // 指向label元素 temp1.click() // 输出: // - public // - public // 测试input点击 temp2 // 指向input元素 temp2.click() // 输出: // - public
原因分析
点击label时,浏览器会自动触发内部input的点击事件。由于data-action绑定在label上,第一次是label本身的点击触发动作,第二次是input的点击事件冒泡到label,再次触发动作——你添加的:stop修饰符只能阻止事件冒泡到label的父元素,无法阻止浏览器自动触发的input点击事件带来的二次触发。
解决办法
方法1:将控制器和动作绑定到input元素(推荐)
把data-controller和data-action移到input上,这样不管点击label还是input,最终都是触发input的点击事件,动作只会执行一次:
<div class="flex flex-1 gap-x-8 justify-start"> <label> <input type="radio" value="public" name="destination[access_type]" id="destination_access_type_public" data-controller="radio-button" data-action="click->radio-button#markAsChecked"> </label> <label> <input type="radio" value="private" name="destination[access_type]" id="destination_access_type_private" data-controller="radio-button" data-action="click->radio-button#markAsChecked"> </label> <label> <input type="radio" value="backend" name="destination[access_type]" id="destination_access_type_backend" data-controller="radio-button" data-action="click->radio-button#markAsChecked"> </label> </div>
方法2:在动作里判断事件触发源
在markAsChecked方法中,只处理label直接被点击的情况,忽略input触发的冒泡事件:
markAsChecked(event) { // 仅当事件目标是label本身时执行逻辑 if (event.target === this.element) { console.log('-', this.element.querySelector('input').value); } }
方法3:使用:once修饰符(仅适用于单次触发场景)
如果只需要动作执行一次,可添加:once修饰符,但多次点击后动作会失效,不推荐用于radio按钮这类需要重复交互的场景:
<label data-controller="radio-button" data-action="click->radio-button#markAsChecked:stop:once"> <input type="radio" value="public" name="destination[access_type]" id="destination_access_type_public"> </label>
内容的提问来源于stack exchange,提问作者Micka
相关产品推荐
相关产品推荐

