禁用 <fieldset> 中 <legend> 中的复选框 onChange 未在 Firefox 中使用 React 触发

IT技术 javascript html reactjs firefox dom
2022-07-05 00:37:40

以下适用于 Firefox 80.0 和 Chromium 84.0.4147.89。

const fieldset = document.getElementById("fieldset");
const toggle = document.getElementById("toggle");

toggle.addEventListener("change", () => {
  if (fieldset.hasAttribute("disabled")) {
    fieldset.removeAttribute("disabled");
  } else {
    fieldset.setAttribute("disabled", true);
  }
});
<form action="#">
  <fieldset id="fieldset">
    <legend>
      <label>toggle <input id="toggle" type="checkbox" /></label>
    </legend>
    <input />
  </fieldset>
</form>

但是,当我尝试在 React 中做类似的事情时,它在 Firefox 中不起作用。禁用字段集后,onChange 事件似乎不会触发。

function App() {
  const [disabled, setDisabled] = React.useState(false);
  const toggleDisabled = React.useCallback(() => {
    setDisabled((disabled) => !disabled);
  }, []);

  return (
    <form action="#">
      <fieldset disabled={disabled}>
        <legend>
          <label>
            toggle <input onChange={toggleDisabled} type="checkbox" />
          </label>
        </legend>
        <input />
      </fieldset>
    </form>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>

<div id="root"></div>

MDN 字段集文章

[当 fieldset 元素被禁用时] 请注意,元素内的表单元素<legend>不会被禁用。

W3C (Example B)WHATWG也提到<legend>不应禁用 的内容。

所以我相信这两段代码的行为方式应该相同:我应该能够disabled使用复选框来切换属性。

如何在 Firefox 80.0+ 上的 React 中实现相同的效果?

1个回答

根据该段前面的 2 行,浏览器似乎期望这种行为。

disabled如果设置了此布尔属性,则所有属于 , 后代的表单控件都将<fieldset>被禁用,这意味着它们不可编辑并且不会与 . 他们不会收到任何浏览事件,例如鼠标点击或焦点相关事件默认情况下,浏览器将此类控件显示为灰色。请注意,元素内的表单元素不会被禁用。-MDN _

Firefox 似乎完全按照它所描述的那样做。
这里的一个解决方案是只放置字段集的<input>外部,这样它就不会受到disabled属性的影响。


编辑

您关于在 DOM 中侦听更高层事件的评论让我开始思考。如果您通过将自己的事件侦听器与良好的 ol'addEventListener结合使用useRefanduseEffect钩子来绕过它,怎么样。创建对复选框的引用并在第一次渲染后侦听更改事件。现在事件正在监听输入本身。
这种“解决方法”似乎确实适用于 FF。

function App() {
  const [disabled, setDisabled] = React.useState(false);
  const toggleElement = React.useRef(null)
  const toggleDisabled = React.useCallback(() => {
    setDisabled(disabled => !disabled);
  }, []);
  
  React.useEffect(() => {
    toggleElement.current.addEventListener('change', toggleDisabled);
  }, []);

  return (
    <form action="#">
      <fieldset disabled={disabled}>
        <legend>
          <label>
          toggle <input ref={toggleElement} type="checkbox" />
      </label>
        </legend>
        <input />
      </fieldset>
    </form>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>

<div id="root"></div>