概要
先日、ソースレビューにて、メソッドチェーンを使えるようクラスを設計すれば、
可読性向上を狙えるよ、とアドバイスいただきました。
自分メモ用に、整理するためにメモ
アドバイス前...
forEachのループ内部、
"template.find(...")
"template.find(...")
"template.find(...")
...
というように変数"template"に対して複数回、操作を行うような実装を行なっている
まあ、これでも機能実装はできていますが、どこか読み辛さを感じますね。。。
※ 以下のJavaScriptは、【Pug】Pugで編集されたファイルを元に、JQueryで値埋め込みのソースの一部です
$(function() {
// APiリクエストなどして取得したデータ例
var list = [
{
date:"2022/09/03",
memo:"",
name:"枝豆"
},
{
date:"2022/09/03",
memo:"hogehoge",
name:"ビール"
}
]
// データ属性指定で、繰り返すDOMを取得する
var templateOriginal = $("[data-js-each=table-list-items]")
// 複製元のDOMを非表示にする(Pugファイルに記載されているのはあくまで”テンプレート”として活用する)
templateOriginal.hide()
list.forEach((item, index) => {
// data属性指定で、複製したDOMを操作する
var template = templateOriginal.clone().wrap('<div>').parent()
template.find('[data-js-text=date]').each((_, elem) => {
$(elem).text(item.date)
})
template.find('[data-js-text=name]').each((_, elem) => {
$(elem).text(item.name)
})
template.find('[data-js-text=memo]').each((_, elem) => {
$(elem).text(item.memo)
})
// 操作したDOMを、指定した箇所の最後に追加して、表示する
template.children().insertAfter("[data-js-each=table-list-items]:last").show()
})
});
アドバイス後!
必要な処理をクラス化して、
"return this"というように、自分自身をreturnすることで、メソッドチェーンができるようになっています
実はこのアプローチは、「fluent interface(流れるようなインタフェース(!))」というもののようです
(!) :
流れるようなインタフェース
第3回 Fluent APIとDbContextの機能
きれいですね!
ただ、Fluent Interface(Fluent API)の考え方は、単に、メソッドチェーン使えるようにするので、"return this"しまーす
ではダメで、その設計思想もしっかり理解しないとですね。これは勉強しないと、、、
ともあれ、今回アドバイスもらって、非常に読みやすくなった例を以下に示します
DOM要素にデータを埋め込むためのクラス"dataEmbedder.js"を定義し、そのクラスを
メソッドチェーンでメソッドを呼び出せるようにしています。
class DataEmbedder {
constructor(rootElement) {
this.rootElement = rootElement
}
text(target, value) {
this.rootElement.find(`[data-js-text=${target}]`).each((_, elem) => {
$(elem).text(value)
})
return this // 自分自身を返すことで、呼び出し側でメソッドチェーンで呼び出せるようにする
}
each(target, list, doAction) {
// 複製元のDOMを非表示にする(Pugファイルに記載されているのはあくまで”テンプレート”として活用する)
const loopElementSelector = `[data-js-each=${target}]`
const targetElement = $(loopElementSelector)
targetElement.hide()
list.forEach((item) => {
let template = this.rootElement
.clone()
.wrap('<div>')
.parent()
// コールバック関数を定義する
// 呼び出し元で、やりたい処理を行う(その際のデータは、"item")
doAction(new DataEmbedder(template), item)
template
.children()
.insertAfter("[data-js-each=table-list-items]:last")
.show()
})
}
}
"dataEmbedder.js.each(...)"をご覧ください
「アドバイス前...」と比べてどうでしょうか。かなりスマートになったと思います。
また、"dataEmbedder.js"というクラスに切り出したことで、別のDOM構造をもつものにもデータ埋め込みを行うことが
でき、より汎用的な処理になったと思います。
index.js
...一部抜粋
$(function() {
// APiリクエストなどして取得したデータ例
var list = [
{
date:"2022/09/03",
memo:"",
name:"枝豆"
},
{
date:"2022/09/03",
memo:"hogehoge",
name:"ビール"
}
]
const dataEmbedder = new DataEmbedder($("[data-js-each=table-list-items]"))
dataEmbedder.each(
"table-list-items",
list,
(listItemEmbedder, item) => {
listItemEmbedder
.text("date", item.date) // メソッドチェーンで、データ埋め込みメソッドを呼び出し
.text("name", item.name) // メソッドチェーンで、データ埋め込みメソッドを呼び出し
.text("memo", item.memo) // メソッドチェーンで、データ埋め込みメソッドを呼び出し
}
)
});
まとめ
今回のアドバイスで、
・ "クラス"のアプローチで共通化を行う
・ メソッドチェーンを使用するために、「fluent interface(流暢な、流れるようなインタフェース)」の考えを持つ
ということを学びました。
またアドバイス受けたら、自分の知識の定着のため、OUTPUTしていきたいと思います!