◆ 関数を渡さずイベントリスナを使う
◆ lit-element の this 問題が起きない
◆ 渡されなかった場合の null チェックいらない
◆ コンポーネント外への影響が少なく済む
◆ DOM らしい書き方
◆ event オブジェクトを経由するので値の取得にひと手間必要

問題点

前に書いたように lit-element ではメソッド呼び出し時に this が問題になるケースがあります

customElements.define("parent-elem", class extends LitElement {
onClickButton() {
console.log(this)
}

render() {
return html`
<button @click=${this.onClickButton}>button</button>
<child-elem .onClickButton=${this.onClickButton}></child-elem>
`
}
})

こういうコンポーネントがあります
button の click イベント時に onClickButton を実行しますが これは lit-element 側で this が CustomElement の要素となるように呼び出してくれます
console.log の this は parent-elem になります

もうひとつ child-elem に渡しているのもありますが これは単純に関数を渡しているだけで bind していない限り parent-elem の情報はなくなります
child-elem では this.onClickButton で参照できるので

this.onClickButton()

のように呼び出すと this は child-elem ですし

<button @click=${this.onClickButton}></button>

のようにリスナに登録すると lit-element の機能で this が child-elem となるように呼び出されます

対処

このまま対処するなら 前回書いたとおり 自分で this を bind しておくしかありません
class のメソッド記法では this は bind されず prototype のプロパティに入りますが アロー関数を代入する形にすれば prototype ではなくインスタンスのプロパティとなりインスタンスに bind 済みです

class Class {
constructor() {
this.method1 = this.method1.bind(this)
}

method1() {}

method2 = () => {}
}

method1 のように書いて constructor で bind したのに置き換えるか
method2 のような書き方にする必要があります

関数を渡さなければ

この対処方法は関数を渡すという前提だからなのですが 関数を渡さないという手も取れます
関数を渡すのは React などでよく見るものですが これらのライブラリはコンポーネント自体が DOM 要素ではないので子コンポーネントからの通知をイベントとして受け取りづらいからです
lit-element は CustomElement を使うので 子コンポーネント自体に簡単にイベントリスナを設定できます

customElements.define("parent-elem", class extends LitElement {
onClickButton() {
console.log(this)
}

render() {
return html`
<button @click=${this.onClickButton}>button</button>
<child-elem @clickbutton=${this.onClickButton}></child-elem>
`
}
})

これだと button と同じように コンポーネント内でリスナを設定しているので this は parent-elem となります

子コンポーネントからの通知の受け取り方で 関数をプロパティとして渡すか イベントリスナとして設定するかで迷ったことはありますが lit-element だとプロパティ渡しが簡単なので React などのようにプロパティ渡しでいいかと思ってました
しかし 今回みたいな this 問題があったのでイベントリスナとして設定するほうがいいかもしれません

関数を渡して外部から呼び出されるよりも 「親コンポーネントはコンポーネント内でリスナを設定するだけ」 「子コンポーネントはイベントをディスパッチするだけ」 のほうがコンポーネント内で完結できて扱いやすい気もします

また 子→親 の通知は常に必要とは限りません
私の場合は基本的に そこ専用のコンポーネントを作っていたので 親コンポーネントから関数を受け取って呼び出す機能があるところはすべて親コンポーネントから関数を受け取る前提でした
しかし 汎用的なものだと 親コンポーネント側で通知を受け取っても特にする処理がないこともありえます
そういうときには関数を渡さないということもありえます
子コンポーネントに関数を渡さない場合があるなら 子コンポーネントでは null チェックが必要です
イベントならリスナがあるかを考えずとりあえず dispatch しておくだけで済みます

それに生の DOM はイベントリスナでクリックや変更を検知します
WebComponents は生の DOM のように扱える要素を作れるものなので 使い方を揃えてイベントリスナ形式にしておいたほうが良い気もします

デメリット

これまで使ってなかった理由でもあるのですが デメリットもあります
関数呼び出しだと直接引数を入れるので 受け取る側が楽です
イベントリスナだと event オブジェクトとして受け取るので そこから必要なデータを取得する追加処理が必要です
event.detail だったり event.target.value などにアクセスしたり 一手間が必要なので

	onSelectItem(event) {
const item = event.detail
this.select(item)
}

のようになります
もし直接 item が引数に入るなら onSelectItem 関数は不要で直接 select をリスナに設定できるのに それができなくなります

detail プロパティを指定メソッドに入れて実行するだけなので 関数にまとめてみても

	constructor() {
this.onSelectItem = this.createListener(this.select)
}

createListener(method) {
return event => {
method.call(this, event.detail)
}
}

結局 constructor で onSelectItem を作るのであまり変わりません

<child-elem @select=${this.createListener(this.select)}></child-elem>

と書くのは毎回新しい関数を作るのとリスナ置き換えが発生するので それなら onSelectItem 関数を作って置いたほうがいいと思います

という感じで一手間増えてしまうのがデメリットです

ただこれは 親も子も自作で そのまま渡せるケースならではです
イベントで受け取った値をチェックしたり変換する必要があるというケースもありますし 将来的に子コンポーネントの仕様変更で必要になるかもしれません
今はたまたまそういった処理がないだけで 子コンポーネントの変更を吸収するために必要なレイヤーと割り切れば基本的にイベントリスナ方式を使うので良いかもしれません