◆ lit-html でテンプレートオブジェクトを作るときに使う変数の更新をどうやるか
◆ 小さければ直接オブジェクト操作でもいい
◆ モジュールに分けると 使い回せるようにしたいけど難しい
  ◆ アプリケーションのオブジェクトを import するとそれに固定される
  ◆ そのモジュールが複数の root に紐づく可能性を考えたい

テンプレートオブジェクトとアプリが紐付かない

規模が大きくないと コンポーネントにするのもコードが増えて面倒なので lit-element なしの lit-html だけということも多々あります
あと ShadowDOM を使うと UI ライブラリと相性が悪いという理由もあったりします

lit-html だけで作るときによく困ることになるのが 関数とアプリケーションの関連付けです
lit-html のタグ関数は テンプレートオブジェクトを返す関数です

const renderButton = (num) => html`<button>${num}</button>`

これ自体は何かに紐付いたりはしていません
それゆえ 更新を行うときに問題が出てきます

ボタンを押したら num をインクリメントできるようにします
1 ファイル中に書いてしまうようなものなら 直接 num の本体がある場所にアクセスできることも多いです

const state = { num: 1 }

const renderButton = (num) => html`<button @click=${() => { state.num++; rerender() }>${num}</button>`

これだと使いまわしづらいですし 1 ファイルが長くなってくるのでモジュールに分けます
そうすると renderButton から直接 state オブジェクトにアクセスできなくなります
そのとき どうやって更新するかが問題になります

直接インポートすると

モジュールがアプリケーションオブジェクトや state オブジェクトをインポートしてしまうことが一番簡単です

import { app } from "./app.js"

export const renderButton = (num) => html`
<button @click=${() => { app.state.num++; app.render() }>${num}</button>
`

ですが そうすると そのアプリケーション専用となってしまいます

たいてい 1 ページ上に 1 つしかありませんが 複数作ろうとしたときに問題になりますし 別の方法を考えたいところです

イベントを使うと

以前は DOM のイベントの仕組みを使ってました

const onlick = (event) => {
Promise.resolve().then(() => {
const e = new CustomEvent("@", { bubbles: true, detail: { type: "num-increment" } })
event.target.dispatchEvent(e)
})
}
export const renderButton = (num) => html`<button @click=${onclick}>${num}</button>`

CustomEvent を dispatch します
lit-html から発火した特別なものとわかるように イベント名は @ で固定して detail に詳細を入れます
lit-html が render 対象とする root 要素で @ イベントを受け取ってそこで state の更新を行います
直接 state を操作しなくても良くなりました

ですが これは DOM イベントに関連づいてるところでしか使えません
onclick の関数を他のタイミングで呼び出そうとしても CustomEvent を dispatch する要素を見つけられません
lit-html の html タグ関数が生成するのはテンプレートオブジェクトで 実際の DOM は受け取りません
どうにか取得しても DOM イベントが起きたタイミング以外だと その要素がすでに破棄されて新規に作り直されている可能性もあります
非同期処理が必要で dispatch 前の await での待機中に remove されるとかありそうです

この方法は イベントバブリングで lit-html が render する root まで伝わることが前提なので ツリーから外れていたらイベントは伝わらず 意味のない処理になってしまいます

こういうケースを考えると イベントを使うのはベストとは思えません
直接 root の要素やアプリケーションを指すオブジェクトを参照したいです

React hook 風にするのは

上で書いたように root 要素やアプリケーションオブジェクトが 1 つとは限らない前提だと固定で import もできません

今呼び出し中のアプリケーションをグローバルに保持して テンプレートオブジェクト作成関数の中で参照することで取得させるという方法もあります
React の hook に近いものです

export const renderButton = (num) => {
const app = getCurrentApp()
return html`<button @click=${() => app.dispatch("num-increment")}>${num}</button>`
}

ですがこの方法は 呼び出す側の管理が面倒ですし それらを行うライブラリ的なレイヤーが必要になります
あと その時々で変わるグローバルなものにアクセスってあまり積極的にしたい方法でもないですし やめておきます

関数の引数

残るは関数の引数で渡すという普通な方法です
ただ 最上位の関数だけなら別にいいのですが 関数内で別のテンプレートオブジェクトを返す関数を呼び出すことだってありえます
何段もネストしたときにずっと渡し続ける必要が出てきます

export const renderContent = (app, values) => html`
<div>${renderSubContent(app, values.sub)}</div>
`
export const renderSubContent = (app, values) => html`
<div>${renderButton(app, values.num)}</div>
`
export const renderButton = (app, num) => html`
<button @click=${() => app.dispatch("num-increment")}>${num}</button>
`

このあたりがちょっといまいちなところです
app の値は変わらない前提なので 都度渡すのではなく最初に渡すことで各 render 関数を受け取れるというかたちにすると少し楽になるのかもです

具体的には こういうモジュールがあったとすると

import { renderButton1 } from "./button1.js"
import { renderButton2 } from "./button2.js"

export const renderButtons = (app, values) => html`
<button @click=${() => app.dispatch("num-increment")}>btn</button>
${renderButton1(app, values)}
${renderButton2(app, values)}
`

こうします

import createRenderButton1 from "./button1.js"
import createRenderButton2 from "./button2.js"

export default app => {
const { renderButton1 } = createRenderButton1(app)
const { renderButton2 } = createRenderButton2(app)

return {
renderButtons: (values) => html`
<button @click=${() => app.dispatch("num-increment")}>btn</button>
${renderButton1(values)}
${renderButton2(values)}
`
}
}

render 関数内では app を渡しません
渡し忘れは起きないですし 必要なものだけを渡すので書きやすいです

ただ 全体的には直接 各 render 関数がエクスポートされてるほうがシンプルだったと思います

結局ベストと言えるほどの方法は思いつきませんし 現実的に複数の root 要素を使うなんてまずないので どの root 要素に紐づくとか考えなくていいような気もしてきました