1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[TypeScript] querySelector/closestの返値の型

Last updated at Posted at 2024-10-22

TypeScriptではquerySelectorclosest'div''li'のようなHTMLの要素名を指定すると、その返値の型はHTMLDivElementHTMLLIElementのように要素名に応じた型となります。

image.png

しかし'div[data-selected]''li:last-child'のような属性セレクタや擬似クラスなどが付いているとElementになってしまいます。

image.png

もしHTMLDivElement型にしようとするなら型アサーションを付ける必要があります。

image.png

しかし

  • 実際にはその要素が見つからないこともあるので | nullを付ける必要がある
  • 補完をミスってHTMLDialogElementにしてしまうかも知れない
  • セレクタによって決まるものをわざわざ書くなんてめんどくさい(本音)

そこでTypeScriptの型関数でどうにかしてみました。

まずセレクタに指定された文字列から対応する要素の型に変換する型関数を用意します。

type ResultQuerySelector<K extends string> = K extends '*'
  ? HTMLElement
  : K extends keyof HTMLElementTagNameMap
  ? HTMLElementTagNameMap[K]
  : K extends `${infer FIRST},${infer REST}`
  ? ResultQuerySelector<FIRST> | ResultQuerySelector<REST>
  : K extends `${string}${' ' | '>' | '+' | '~' | '|'}${infer KK}`
  ? ResultQuerySelector<KK>
  : K extends `${infer KK}${'.' | '[' | '#' | ':'}${string}`
  ? KK extends `${string}${'.' | '[' | '#' | ':'}${string}`
    ? never
    : KK extends keyof HTMLElementTagNameMap
    ? HTMLElementTagNameMap[KK]
    : HTMLElement
  : never;

querySelectorの定義にはSVGやMathMLのものもありますが、とりあえずHTMLElement派生のものだけに限定しています。必要なら上記のHTMLElementTagNameMapを置き換えてみてください。

次にquerySelector(ついでにquerySelectorAllも)やclosestの返値をこの型に差し替えます。

interface ParentNode {
  querySelector<K extends string>(selectors: K): ResultQuerySelector<K> | null;
  querySelectorAll<K extends string>(
    selectors: K,
  ): NodeListOf<ResultQuerySelector<K>>;
}

interface Element {
  closest<K extends string>(selector: K): ResultQuerySelector<K> | null;
}

こうすることで属性セレクタや擬似クラスなどが付いているセレクタでも適切な型で返ってきます。

image.png

兄弟結合子やセレクターリストにも対応しています。

image.png

image.png

ここではモジュールを使っていないのでそのままで使えていますが、モジュールを使う場合にはdeclare global {}で囲う必要がありますのでご注意を。

2024/10/23 22:45追記

書き忘れてました。

:hasなど引数にセレクタをとる擬似クラス関数を使っていると誤判定することがあります。

image.png

といってもセレクタを引数にとる擬似クラス関数は、CSSでは使ってもスクリプト側で使うことはあまりないでしょう。(と信じたい)

1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?