🌟
JSのクラスメソッドをonclickに設定するときにつまずいたこと
JS(TS)のクラスメソッドを HTMLButtonElement の onclick に設定するときにつまずいて 1 時間ぐらい溶けたので備忘録として残しておきます。
前提
webpack + babel を使ってトランスパイルをしています。
package.json
package.json
{
...
"scripts": {
"build": "webpack --config webpack.config.prod.js",
"dev": "webpack --config webpack.config.dev.js --watch"
},
...
"browserslist": [
"defaults,not ie >= 0"
]
}
.babelrc
.babelrc
{ "presets": ["@babel/preset-env"] }
babel.config.js
babel.config.js
module.exports = (api) => {
api.cache(true);
return {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage",
corejs: 3,
},
],
"@babel/typescript",
],
};
};
webpack.config.js
webpack.config.dev.js
とwebpack.config.prod.js
は下のをベースに少しいじったものです。
webpack.config.js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
/** @type import('webpack').Configuration */
module.exports = {
mode: 'none',
entry: './ts/index.ts',
target: ["web", "es5"],
output: {
path: path.join(__dirname, "dist"),
filename: "index.js"
},
module: {
rules: [{
test: /\.ts$/,
use: ["babel-loader"],
exclude: /node_modules/,
}]
},
resolve: {
extensions: [".ts", ".js", ".tsx"]
},
plugins: [
new CleanWebpackPlugin()
]
};
状況
実装をだいぶ省略して書くとこんな感じのものになります。
GetElement
export module GetElement {
export function getHTMLInputElement(id: string): HTMLInputElement | never {
const element = document.getElementById(id);
if (!element) throw Error(`element ${id} does not exist`);
return element as HTMLInputElement;
}
}
Hoge.ts
import { GetElement } from './GetElement';
export class Hoge {
hogeInputId: string;
constructor(hogeInputId: string) {
this.hogeInputId = hogeInputId;
}
action(): void {
const hoge = GetElement.getHTMLInputElement(this.hogeInputId).value;
}
}
index.ts
import { Hoge } from './Hoge';
import { GetElement } from './GetElement';
window.onload = () => {
const hoge = new Hoge('hoge');
GetElement.getHTMLButtonElement('hogeButton').onclick = hoge.action;
};
これをブラウザーで開いてボタンを押すと次のようなエラーが出ました。
エラー文
Uncaught Error: element "undefined" not found
at getHTMLElement
at getHTMLElementAs
at Object.getHTMLInputElement
at HTMLButtonElement.action
hoge
の定義時にはちゃんと id を定義にしているのでundefined
になるのはおかしいと思います(小並感)。
なぜこうなってるのか不明なのでログに書き出してみました。
hoge.ts
--- a/ts/hoge.ts
+++ b/ts/hoge.ts
@@ -4,8 +4,10 @@ export class Hoge {
hogeInputId: string;
constructor(hogeInputId: string) {
this.hogeInputId = hogeInputId;
+ console.log(this);
}
action(): void {
+ console.log(this);
const hoge = GetElement.getHTMLInputElement(this.hogeInputId).value;
}
}
するとコンストラクタ内の方では正しく自分が吐き出されているものの、action
の方では HTML の要素が出てきました。
console.log(this);
<input class="monofont validate" id="hoge" type="text" placeholder="..." value="..." required aria-required>
こんなこともしてみました。
変数の名前をほかのもの(hoge2
)に変えてhoge
,hoge2
をログに出す。
index.ts
--- a/ts/index.ts
+++ b/ts/index.ts
@@ -2,7 +2,9 @@ import { Hoge } from './Hoge';
import { GetElement } from './GetElement';
window.onload = () => {
- const hoge = new Hoge('hoge');
+ const hoge2 = new Hoge('hoge');
- GetElement.getHTMLButtonElement('hogeButton').onclick = hoge.action;
+ GetElement.getHTMLButtonElement('hogeButton').onclick = () => hoge2.action();
+ console.log(hoge);
+ console.log(hoge2);
};
するとhoge
はさっきの HTML の要素、hoge2
は Hoge のインスタンスでした。
訳が分からずいろいろしてみましたが、なかなか解決できませんでした。
解決策
無名関数で覆う
--- a/ts/index.ts
+++ b/ts/index.ts
@@ -4,5 +4,5 @@ import { GetElement } from './GetElement';
window.onload = () => {
const hoge = new Hoge('hoge');
- GetElement.getHTMLButtonElement('hogeButton').onclick = hoge.action;
+ GetElement.getHTMLButtonElement('hogeButton').onclick = () => hoge.action();
};
これだけで解決しました。
問題点
- 1.onclick に直接クラスメソッドをくっつけたこと
- 2.html の id は同名の変数が作成されることを知らなかったこと
1
勝手な考察ですが、おそらく onclick に渡された関数にはその要素がバインドされるのでしょうか。それでthis
が上書きされているのだと思います。
this
が失われたわけではないと思います。(∵ ログに HTML 要素が出ている)
2
これに関しては調べたら出てきました。
個人的な感想ですがこの機能いらないと思います。混乱するので。
Discussion