React.jsでdisabledなラジオボタン/チェックボックスのイベント挙動がIE 11だけおかしい件


React.jsを使っていてIE 11固有の問題に遭遇してしまったので共有しておく。

遭遇した問題

下記のようなコードを書いていた。

class Sample extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            selected: false;
        };
        this.handleChange = this.handleChange.bind(this);
    }

    render() {
        return (
            <input 
              disabled={true}
              type="radio"
              checked={this.state.selected} 
              onChange={this.handleChange}  />
        );
    }

    handleChange(e) {
        this.setState({
            selected: true
        });
    }
}

disabled={true}をラジオボタンに設定し、クリックしても選択不可な状態にしている。が、Chrome/Firefoxは問題ないが、IE 11ではラジオボタンをダブルクリックするとonChangeで設定した関数(handleChange)が呼ばれてしまう。上記のように「onChangeが呼ばれる == 選択された」 としてsetStateするように実装してしまっていると、disabledなのに選択されてしまう、というバグを生み出してしまう。

原因調査

切り分けのためReact.jsを使わずに再現コードを書いてみた。disabledなラジオボタンを1つ置き、clickイベントの発火回数を画面に出力している。addEventListenerによるイベントリスナーの登録を、

  • inputタグ
  • inputタグの親(例だとdivタグ)

に設定している。

再現コード
<div id="parent">
  <input id="input" type="radio" disabled />
</div>
Event "input": <span id="output1">0</span><br/>
Event "parent": <span id="output2">0</span>
<script type="text/javascript">
document.getElementById("input").addEventListener("click", change1);
document.getElementById("parent").addEventListener("click", change2);

var count1 = 0;
var count2 = 0;
function change1(e) {
  document.getElementById("output1").innerHTML = ++count1;
}
function change2(e) {
  document.getElementById("output2").innerHTML = ++count2;
}
</script>

これをIE11で実行し、ラジオボタンをダブルクリックするとEvent "parent"のカウンタだけが増加する。Chrome、FireFoxだとダブルクリックしても増加はせず、IE11固有の挙動であることが分かった。どうやらIE11は親要素に登録されたイベントリスナーに対しては、ターゲット要素がdisabledであってもイベントを伝播させてしまうようだ。この挙動はラジオボタンに限らずチェックボックスでも同じであった。また、ボタンの場合はシングルクリックでもIE11だけイベントが発火されるようである。。

React.jsの場合、イベントリスナーは全要素の親に位置するdocumentオブジェクトに登録されている。そのため、この再現コードのようにdisabledなラジオボタンであってもダブルクリックによりイベントが発火されてしまうようである。

React.jsの場合はボタンは大丈夫

再現コードではボタンであっても同事象が起きるが、React.jsでdisabledなボタンを使ってもイベントは発火されない。これは、ボタンに関しては ReactDOMButton.jsにてdisabledの場合はマウスイベントをpropsから除く処理が行われているため。

一方、ラジオボタンやチェックボックスなどのinputタグの実装であるReactDOMInput.jsでは同様の処理が入っていないため問題が発生する。

React.jsの対応状況

本問題はコミュニティでも既に認識済みで、#4457 IE11 block change events if disabledとしてIssue登録されている。これを見ると2015年末から動きがなさそうだが、色々漁って見ると プルリクエスト Disabled inputs should not respond to clicks in IE #6215を発見した。このプルリクの変更内容を見るとReactDOMInput.jsに対しても修正が行われており、マージされれば本問題は解決しそうである。

ワークアラウンド

React.js側で対応されるまでは、アプリケーションコード側でチェック処理を入れれば一応問題の回避は可能である。例えば、前述のサンプルコードだとhandleChange関数に下記のようなdisabledをチェックするコードを入れれば良い。または、e.target.checkedの値を見るでも良い。

    ...
    handleChange = (e) => {
        // disabledなら無視
        if (e.target.disabled) {
            return;
        }
        this.setState({
            selected: true
        });
    }
}

React.js側に自分でパッチを当てたい場合はプルリクエストを参考にすれば良いと思う。