JavaScriptのthisとReactでのイベントハンドラのバインド
ある企業のコーディングテストで、JavaScriptのクラスメソッドに関する問題が出題されて、そのときにthis
に関して改めて調べてみると学びがあったのでメモを残しておきます。
import reportWebVitals from "./reportWebVitals";
import * as ReactDOM from "react-dom";
import { Component, ClassAttributes } from "react";
interface CounterProps extends ClassAttributes<Counter> {
initialCount: number;
}
class Counter extends Component<CounterProps, any> {
constructor(props: CounterProps) {
super(props);
this.state = {
count: props.initialCount,
};
}
handleAddClick() {
this.setState({ count: this.state.count + 1 });
}
handleDownClick() {
this.setState({ count: this.state.count - 1 });
}
render() {
const { count } = this.state;
return (
<div>
<h1>{count}</h1>
<div>
<button onClick={this.handleAddClick}>up</button>
<button onClick={this.handleDownClick}>down</button>
</div>
</div>
);
}
}
ReactDOM.render(
<Counter initialCount={0} />,
document.getElementById("root")
);
突然ですが上記のコードをみて、どこが問題なのかわかりますか?
この問題が分かる人はこの記事は読まなくても大丈夫です。
イベントハンドラが動かない理由
今回イベントハンドラが動かない理由はthis
にあります。
先に結論を言うと、this
の値がundefined
になってしまっているのです。
JavaScriptは通常の関数を宣言すると内部にthis
が設定されます。
これにより、関数の呼び出し方によってその関数の内部でthis
は呼び出し元のオブジェクトに設定されます。
しかし、Reactはイベントハンドラを呼び出すときにthis
をコンポーネントインスタンスに設定しないためthis
の値がundefined
になってしまっているのです。
この問題を解決するための一つのが、手動でイベントハンドラをコンポーネントインスタンスにバインドすることです。
constructor(props: CounterProps) {
super(props);
this.state = {
count: props.initialCount,
};
this.handleAddClick = this.handleAddClick.bind(this);
this.handleDownClick = this.handleDownClick.bind(this);
}
このようにコンストラクタでイベントハンドラをバインドすることで、this
の値をコンポーネントインスタンスに設定することができます。
this
の挙動
アロー関数における実はもう一つの解決方法があります。
それはアロー関数を使うことです。
アロー関数は内部にthis
を持ちません。
アロー関数では通常の関数宣言と違い関数が呼び出された時ではなく、関数がどこで定義されたかによってthis
が設定されます。
そのため、関数定義をアロー関数に変更することでthis
の値をコンポーネントインスタンスに設定することができます。
```ts
handleAddClick() {
this.setState({ count: this.state.count + 1 });
}
handleDownClick() {
this.setState({ count: this.state.count - 1 });
}
this
の挙動が複雑な理由
JavaScriptのJavaScriptのthis
の挙動が複雑な理由は、JavaScriptがプロトタイプベースの言語だからです。
プロトタイプベースのプログラミング言語とは、プロトタイプ(すでに存在するオブジェクト)を複製して新しいオブジェクトを作成するプログラミング言語です。
JavaScriptでは新しいオブジェクトはプロトタイプのプロパティや、メソッドを継承します。
さらに、新しいオブジェクトはプロトタイプのプロパティやメソッドを上書きすることができます。
let animal = {
eats: true,
walk() {
console.log("Animal walk");
},
};
let rabbit = Object.create(animal); // 'animal' object is the prototype of 'rabbit'
rabbit.walk(); // outputs "Animal walk"
このコードではanimal
オブジェクトをプロトタイプとしてrabbit
オブジェクトを作成しています。
その結果、rabbit
オブジェクトはanimal
オブジェクトのwalk
メソッドを継承しており、それを呼び出すことができます。
プロトタイプベース言語は柔軟性が高く、動的にオブジェクトを作成することができます。
しかし、一方でプロトタイプベース言語はその柔軟性ゆえにコードが予測しにくい原因にもなります。
this
の挙動を調べることで、JavaScriptの言語仕様を1つ理解することができました。
Discussion