前回記事で作成したプログラムは、関数の定義はあまりせずにハードコーディングしているため、同じようなコードが2回書かれている見通しの悪いプログラムでした。
一度作成して動いているコードでも可視性や保守性を上げるためや、効率の良いプログラムにするために後でコードのブラッシュアップをすることをリファクタリングと言います。
その日は必死になって作って「やっと動いた!」と感動したプログラムも、翌日見てみると「ひっどいな、これ・・・」となるのはよくあることなので、そこで挫けずに良いプログラムにすることが大事です。
先輩のエンジニアさんがいるような方でしたら見せるのを怖がらずレビューしてもらうことで、意味のない記述だったり、効率の悪い書き方だったりを指摘してもらえると思いますので、ぜひレビューをしてもらいましょう。
今回のリファクタリングはクラス構文に書き換えていきます。
クラス構文
クラスはJavaScriptに限らず、さまざまな言語で使われているプログラムの設計図です。もともとJavaScriptにクラスはなかったのですが、ES2015の時に追加されています。
他のプログラミング言語をやっている人にとっては理解しやすいのですが、HTML、CSS→JavaScriptと進んできている人にとっては解釈が難しいところがあるようです。細かい説明については割愛しますが、文末に参考資料のリンクを載せておきます。
ここではクラス構文に書き直すとどうなるかを作りながら見ていきましょう。
クラスの宣言、クラス式
クラスの宣言、クラス式には class をにつけて下記のように記述します。
class クラス名 { 処理を記述 }
// クラス式の場合
const クラス名 = class { 処理を記述 }
クラスの宣言とクラス式は似ていますが、再宣言が可能・不可能などいくつかの相違点があるようです。ここではクラスでの記述方法を先に学んでいきたいので、興味のある方は文末の参考記事をご確認ください。
constructor
クラスにはconstructorと言われる、インスタンス化した際の最初に実行される初期化のメソッドがあります。
class Hello {
constructor(name) {
this.name = name;
}
goodMorning() {
alert("Good Morning " + this.name)
}
goodEvening() {
alert("Good Evening " + this.name)
}
}
// インスタンス化
const hello = new Hello("Taro");
hello.goodMorning();
hello.goodEvening();
上記のプログラムではインスタンス化の際にconstructorのnameにTaroを渡しています。
その後にgoodMorningとgoodEveningのメソッドを使う時には、初期化時に代入した変数が出力されているということです。
メソッド定義
constructor以外にも通常のメソッドをクラスの内容として定義していきます。すでに上記のプログラムで使っていますが、goodMoning、goodEveningの部分になります。
初期化後の処理はこのようにメソッドを作成して記述していきます。通常の関数と違うのはクラス内でメソッドを呼び出す際には this をつけて呼びだす必要があります。下記のプログラムではconstructorで代入した個数、金額、税率を使って、小計、消費税額を算出後にブラウザのアラートで計算結果を出力しています。
class PriceSum {
constructor(num, price, taxRate) {
this.num = num;
this.price = price;
this.taxRate = taxRate;
}
subTotal() {
const value = this.num * this.price
return value;
}
tax() {
const value = this.subTotal() * this.taxRate;
return value;
}
total() {
alert("お会計は" + (this.subTotal() + this.tax()) + "円(内消費税 " + this.tax() + "円)です。" )
}
}
const priceSum = new PriceSum(2, 200, 0.08);
priceSum.total() // お会計は432円(内消費税 32円)です。
thisを記述しないで使おうとすると「メソッド名 tax is not defined」のエラーがコンソールに出力されますので、メソッドを使うときに気を付けておきましょう。
リファクタリング
前回のコードをクラス構文でリファクタリングしていきます。前回のプログラムはGitHubに上げてありますので、そちらを参考にしてください。
otoiron/web-dukuri-study | GitHub
リファクタリング後のコードは下記のようになりました。先に全体を見ていただいて、部分ずつ解説していきます。
class ListCreate {
constructor() {
const listItem = document.querySelectorAll(".list__item");
this.add();
listItem.forEach(value =>{
const el = this.elements(value);
this.delete(el, value);
this.edit(el, value)
this.submit(el, value);
})
}
// リストの要素をオブジェクトに代入
elements(listItem) {
const element = {
itemBody: listItem.querySelector(".list__itemBody"),
itemDeleteButton: listItem.querySelector(".button--delete"),
itemEditButton: listItem.querySelector(".button--edit"),
itemSubmitButton: listItem.querySelector(".button--submit")
}
return element;
}
// 編集
edit(el, listItem) {
el.itemEditButton.addEventListener("click", () => {
const inputForm = document.createElement("input");
inputForm.type = "text";
inputForm.className = "inputText";
inputForm.value = el.itemBody.textContent;
el.itemBody.replaceChildren(inputForm);
el.itemDeleteButton.classList.add("hidden");
el.itemEditButton.classList.add("hidden");
el.itemSubmitButton.classList.remove("hidden");
});
}
// 編集決定
submit(el, listItem) {
el.itemSubmitButton.addEventListener("click", () => {
const inputForm = listItem.querySelector(".inputText");
el.itemBody.replaceChildren(inputForm.value);
el.itemDeleteButton.classList.remove("hidden");
el.itemEditButton.classList.remove("hidden");
el.itemSubmitButton.classList.add("hidden");
});
}
// 削除
delete(el, listItem) {
el.itemDeleteButton.addEventListener("click", () => {
listItem.remove();
});
}
// 追加
add() {
const list = document.querySelector(".list");
const itemAddButton = document.querySelector(".button--add");
itemAddButton.addEventListener("click", () => {
const listItem = document.createElement("li");
listItem.className = "list__item";
listItem.innerHTML = `
<div class="list__itemBody">テスト</div>
<div class="list__itemButtons">
<div class="button button--delete">削除</div>
<div class="button button--edit">編集</div>
<div class="button button--submit hidden">決定</div>
</div>
`;
const el = this.elements(listItem);
this.delete(el, listItem);
this.edit(el, listItem)
this.submit(el, listItem);
list.appendChild(listItem);
})
}
}
new ListCreate();
まずはクラス宣言です。このクラス名はインスタンス化の時に使います。クラス名は先頭大文字のキャメルケースとすることが「Google JavaScript Style Guide」でも言われていますので、倣って記述していきましょう。
class ListCreate {...}
初期化
続いて初期化のconstructorメソッドの内容を記述しています。「.list__item」は引数で取った方が汎用性が上がりますが、もっとリファクタリングする必要があるので今回は直書きにしました。
初期化の主な処理はボタンの機能ごとに振り分けたメソッドを使用しています。HTML上に記載されている要素へのイベントの関連付けを行なっています。
constructor() {
const listItem = document.querySelectorAll(".list__item");
this.add(); // 追加ボタン
listItem.forEach(value =>{
const el = this.elements(value); // ボタンの要素を取得
this.delete(el, value); // 削除ボタン
this.edit(el, value) // 編集ボタン
this.submit(el, value); // 編集後の決定ボタン
})
}
リスト内のボタン要素の取得
通常メソッドの1番目はリスト内に含まれる要素の取得です。constractorメソッドで取得したHTML上に配置されたボタン要素などを変数に格納しています。リファクタリング前のプログラムでは追加ボタンの方にも同じプログラムを使っていたので、メソッド化して使い回しています。
elements(listItem) {
const element = {
itemBody: listItem.querySelector(".list__itemBody"),
itemDeleteButton: listItem.querySelector(".button--delete"),
itemEditButton: listItem.querySelector(".button--edit"),
itemSubmitButton: listItem.querySelector(".button--submit")
}
return element;
}
編集、決定、削除
次に編集、編集の決定、削除のボタンをそれぞれメソッドにしていきます。コメントアウトもつけていますが、メソッド名を見るだけでどこに何の処理が書かれているかがわかりやすく、見通しが良くなっていると言えるのではないでしょうか。
コードの内容にあまり変化はありませんが、メソッドに引数で要素を渡しています。
// 編集
edit(el, listItem) {
el.itemEditButton.addEventListener("click", () => {
const inputForm = document.createElement("input");
inputForm.type = "text";
inputForm.className = "inputText";
inputForm.value = el.itemBody.textContent;
el.itemBody.replaceChildren(inputForm);
el.itemDeleteButton.classList.add("hidden");
el.itemEditButton.classList.add("hidden");
el.itemSubmitButton.classList.remove("hidden");
});
}
// 編集決定
submit(el, listItem) {
el.itemSubmitButton.addEventListener("click", () => {
const inputForm = listItem.querySelector(".inputText");
el.itemBody.replaceChildren(inputForm.value);
el.itemDeleteButton.classList.remove("hidden");
el.itemEditButton.classList.remove("hidden");
el.itemSubmitButton.classList.add("hidden");
});
}
// 削除
delete(el, listItem) {
el.itemDeleteButton.addEventListener("click", () => {
listItem.remove();
});
}
追加
最後に追加のaddメソッドを記述しています。リファクタリング前のコードではページ読み込み時と、リスト追加時にハードコーディングで似たような記述をしていましたが、今回はメソッドを使い回しているので同じ処理をしていることが一目瞭然です。
// 追加
add() {
const list = document.querySelector(".list");
const itemAddButton = document.querySelector(".button--add");
itemAddButton.addEventListener("click", () => {
const listItem = document.createElement("li");
listItem.className = "list__item";
listItem.innerHTML = `
<div class="list__itemBody">テスト</div>
<div class="list__itemButtons">
<div class="button button--delete">削除</div>
<div class="button button--edit">編集</div>
<div class="button button--submit hidden">決定</div>
</div>
`;
const el = this.elements(listItem);
this.delete(el, listItem);
this.edit(el, listItem)
this.submit(el, listItem);
list.appendChild(listItem);
})
}
終わりに
クラス構文を使ってリファクタリングはどうでしたでしょうか? もっと良いプログラムにするためには汎用性を持たせるためにHTMLのclass名を引数で渡すようにしたり、追加時の要素もHTML上にコンポーネントを記述しておくなど、まだまだやれることはあります。
作ったコードを勉強が進んだ後にもう一度確認して、もっと良くするためにはどうしたら考えることもプログラムの力をつける練習になると思います。リファクタリングができることで成長に気づけて自信に繋がることもあります。
ここまで読んでくださり、ありがとうございました。
本記事で記述したコード
otoiron/web-dukuri-study | GitHub
参考記事
JavaScriptのクラス(class)を理解する – TECH PLAY
ES2015新機能: JavaScriptのclassとmethod | Qiita
制作協力
サムネイル制作:うみ Twitter
コメント