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

Django+JS中addEventListener执行两次致单选框状态异常问题

解决Django表单分类切换时点击事件触发两次的问题

问题场景

用Django和JavaScript开发表单创建功能,需要点击分类标签切换单选框的checked属性来更改条目分类,但点击secondLabel时addEventListener会执行两次,导致checked属性刚被添加就立即移除。需求是点击secondLabel时,首次切换到分类2,再次点击切回分类1,循环切换。

原代码

Django表单模板

<div class="new-post-header">
    <div class="new-post-functions">
        <button type="submit" class="save"><ion-icon name="checkmark-outline"></ion-icon></button>
        <div class="categories">
            <div class="first-category">{{ form.category.0 }}</div>
            <div class="second-category">{{ form.category.1 }}</div>
        </div>
    </div>
</div>

<div class="new-post-content">
    <div class="new-title">{{ form.title }}</div>
    <div class="new-description">{{ form.description }}</div>
</div>

原JavaScript代码

const firstLabel = document.querySelector('[for="id_category_0"]')
const secondLabel = document.querySelector('[for="id_category_1"]')
const firstInput = document.querySelector('input[value="1"]')
const secondInput = document.querySelector('input[value="2"]')

secondInput.hasAttribute('checked')
? localStorage.setItem('secondInput', true)
: localStorage.setItem('secondInput', false)

secondLabel.addEventListener('click', function() {
    let secondInputItem = localStorage.getItem('secondInput')
    if (secondInputItem === 'false') {
        firstLabel.innerHTML = `<input type="radio" name="category" value="1" required="" id="id_category_0"><ion-icon name="bookmark-outline"></ion-icon>`
        secondLabel.innerHTML = `<input type="radio" name="category" value="2" required="" id="id_category_1" checked="checked"><ion-icon name="archive-outline"></ion-icon>`
        localStorage.setItem('secondInput', true)
        console.log(true)
    } else {
        firstLabel.innerHTML = `<input type="radio" name="category" value="1" required="" id="id_category_0" checked="checked"><ion-icon name="bookmark-outline"></ion-icon>`
        secondLabel.innerHTML = `<input type="radio" name="category" value="2" required="" id="id_category_1"><ion-icon name="archive-outline"></ion-icon>`
        localStorage.setItem('secondInput', false)
        console.log(false)
    }
})

问题原因

  1. 每次点击都通过innerHTML重新渲染label内部元素,破坏原有DOM结构,导致事件触发逻辑异常;
  2. 手动修改HTML结构切换选中状态,忽略了radio按钮的原生互斥特性(同一name下的radio会自动取消其他选项的选中状态);
  3. localStorage存储布尔值时会自动转为字符串,直接用字符串判断容易出现类型匹配问题。

解决方案

优化后的JavaScript代码(保留localStorage状态)

const firstInput = document.querySelector('input[value="1"]');
const secondInput = document.querySelector('input[value="2"]');
const secondLabel = document.querySelector('[for="id_category_1"]');

// 初始化localStorage,用JSON处理布尔值类型
const isSecondChecked = secondInput.checked;
localStorage.setItem('secondInput', JSON.stringify(isSecondChecked));

secondLabel.addEventListener('click', function(e) {
    // 阻止事件冒泡,避免额外触发
    e.stopPropagation();
    
    // 解析localStorage中的布尔值
    let secondInputItem = JSON.parse(localStorage.getItem('secondInput'));
    
    if (!secondInputItem) {
        secondInput.checked = true;
        localStorage.setItem('secondInput', JSON.stringify(true));
    } else {
        firstInput.checked = true;
        localStorage.setItem('secondInput', JSON.stringify(false));
    }
});

极简版(无需localStorage,直接依赖radio状态)

如果不需要页面刷新后保留选中状态,可简化为:

const firstInput = document.querySelector('input[value="1"]');
const secondInput = document.querySelector('input[value="2"]');
const secondLabel = document.querySelector('[for="id_category_1"]');

secondLabel.addEventListener('click', function(e) {
    e.stopPropagation();
    // 直接根据当前选中状态切换,radio互斥特性会自动处理另一个选项
    secondInput.checked = !secondInput.checked;
});

优化说明

  • 不再修改innerHTML,直接操作input的checked属性,保留原有DOM结构,避免事件触发异常;
  • 利用radio按钮的原生互斥特性,无需手动修改两个input的状态,简化代码逻辑;
  • 用JSON.stringify和JSON.parse处理localStorage的布尔值,确保类型判断准确;
  • e.stopPropagation()阻止事件冒泡,避免父元素事件或浏览器原生label行为导致的额外触发。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 15:45:20