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側に自分でパッチを当てたい場合はプルリクエストを参考にすれば良いと思う。