TypeScript と HTMLElement と style と
TypeScript における HTMLElement の型指定
ついでに、と言っては何だけれどもうひとつ TypeScript のねたを。
正直に言って、TypeScript に触る前まで Javascript において取得した HTML の要素の型など考えたこともなかった。オブジェクトであることはわかっていたけれど。
しかし、TypeScript に関わるつもりならば、そのあたりのこともしっかり頭に入れておかないといけないようだ。
たとえば、よく使うものとしては、こんなのを2つ。
id と class で要素を取得した場合の違い。単体と複数ということからしてやはり型は違わないといけないのだろう。
そして大枠として HTMLElement があるのだけれど、各タグによってそれぞれに個別の型がある。たとえば、下のように input タグが存在しているとしてその要素を取得する。
input 要素を HTMLElement の型で取得はできるが、”value” というプロパティは持っていないので使えず、”getAttribute” なら持っているから使えるということ。であるなら、この場合、取得するのが inputタグだとわかっているのだから、取得するときの型に ”HTMLInputElement” を指定してやれば済むことではないか、と思うものの “getElementById” の戻り値の型自体が ”HTMLElement | null” のようで、
「型 ‘HTMLElement | null‘ を型 ‘HTMLInputElement | null‘ に割り当てることはできません”。」
と、怒られてしまうことになる。取得するときにアサーションする手もあるけれど、それは先でも後でも同じこと。
ここでちょっと横道にそれるが、Element.value と Element.getAttribute( ‘value’ ) は挙動が全く同じではないので要注意なのであるが、細かいことをよく忘れてしまうので・・・。たとえば、下のようなことをすると違いが出る。
要は、Element.getAttribute( ‘value’ ) で取得した値は、ページロード時の初期値の値であり、その後、”Element.value = ” で値を更新したり、ユーザーがブラウザに直接入力して値を更新しても変更されない。この値が更新されるのは setAttribute( ‘value’ ) で更新した場合だけになる。
一方、Element.value においては、全くその逆で、”Element.value = “で値を更新した場合はもちろん、ユーザーがブラウザに直接入力して値を更新した場合にも更新される。しかし、setAttribute( ‘value’ ) にての値の更新においては、変更されない。そしてこの Element.value の値が、ブラウザの input 要素に表示されている値とシンクロしている。
と、いうことで、通常はブラウザに表示されているフォームとシンクロしている同じ値が必要なはずであるから、Element.value を使うべきで、その要素の初期値が欲しい場合などは、getAttribute( ‘value’ ) で取得できるという感じだと思う。
HTMLElement の型には ”value” プロパティがないけれど、HTMLInputElement には存在する、ということでちょっと気がつくのは prototype の存在。
ためしに取得した input 要素のオブジェクトを console.log( Object ) で表示させてみると、そのオブジェクトの持っているプロパティがどどっと列挙されて表示される(ただし、これは Firefox の場合、Chrome だと console.dir( Object ) になる)。
表示されるプロパティはたくさんあるが、その中に ”HTMLInputElement”、そして prototype において ”HTMLElement” の文字列を見つけることができる。まぁ、”HTMLElement” において個別の型の文字列がよくわからないときは console を使えばわかるということがわかった。
ちなみに、個別の prototype の名前だけを得たいなら、下で良さそう。これは Firefox、Chrome ともに共通。これにて ”HTMLInputElement” の文字列だけが得られる。
HTML のタグにおいて共通するプロパティやメソッドなどは、大本の ”HTMLElementprototype” に設定してあって、それぞれのタグに特別なもの、たとえば input タグの “value” のようなものは、それぞれ専用の prototype である ”HTMLInputElement” のようなものが用意されているという認識でいいのではないだろうか。
ところで、はじめのうちは、これの型は何を指定すれば?と、いうことには多く出くわすのだろう。
そんなときは、知りたい型の対象を取得して、console.log( ) で typeof してみることが一つ。もう一つは、絶対に思ったものとは違うであろう型を指定した変数に、知りたい型の対象を代入してみる。VSCord でエラーがでて、そのエラーの内容で大抵は型がわかるはず。innerHTML で得られるのはただの文字列なのか、はたまたオブジェクトなのかと思ったことがあった。
それにしてもどうにもしっくりこないのだけれど、「プロパティ ‘value‘ は型 ‘HTMLElement‘ に存在しません。」というエラーは必要なのだろうか。コンパイラにそれと教えてやるということではあるけれど、アサーション自体がこれもごまかしのようなものだろうに。もともとこの部分において Javascript で挙動がおかしくなったりするものでもないだろうに、アサーションでごまかしていいのなら、はじめからすべて HTMLElement でいいではないか。と、思ってしまうのである。
変数によるブラケット記法での css style の変更
たとえば、Javascript において要素の style の設定をする場合、その設定したい style のプロパティが決まっている場合、通常はドット記法によって書くのが簡単である。
しかし、そのプロパティ名を変数で指定したい場合はこのドット記法では書くことができない。その場合は、[] を使ったブラケット記法で書くことができる。まぁ、なんてことはなく、普通のオブジェクトの扱いとまったく同じである。
Javascript において複数の style 設定をまとめて行う場合、Element.style.cssText を使うのが最も高速である。しかし、この cssText が高速なのは、単に ”=” で使った場合であり、その場合、同じ要素に対して設定してある既存のインラインでのスタイル設定( Javascript で設定したスタイルはすべてインラインである)をすべて白紙にして、新しいものだけにしてしまう。その対策として、”+=” で設定すれば白紙にせずに上書きできるのであるけれど、その場合速度的に極端に遅くなってしまい、cssText を使わず個々のプロパティに対して個別に設定したほうが断然速いということになってしまう。
そういう場合に、複数のスタイル設定を連想配列に仕込んでおいて、プロパティ名を変数で指定しつつループさせる場合に、このブラケット記法が必要になってくるというわけだ。そしてその場合、いまのところ(2022/05)hasOwnProperty の条件式を入れた for-in ループが最も高速である。例えばこんな具合に。が、しかしこれは17行目がエラーとなってしまう。なぜか?
エラーを読むと、key においてインデックス式が型 ‘number’ ではないため、とある。なんだそれ!である。これまでの経験からしてこの key は ‘string’ が常識ではないのか?という疑問にぶつかる。「要素に ‘any’ 型が暗黙的に指定されます。」って、一体どの要素が指定されているのだろうか。
この件は、ネットを探せば答えが見つかり、面倒なので詳細は省くが、TypeScript における CSSStyleDeclaration の仕様の都合によるのだそうだ。ということで、対策も2つほど知ることができる。
1つ目は、対象の要素を ‘any’ 型にアサーションしてしまう方法。要素が ‘any’ 型に暗黙的に指定されてしまうのなら、いっそ明示的にこちらから対象を any 型にアサーションしてしまえば良いってことだろうか。確か ‘any’ 型はその要素に対しての型チェックがされないということではなかったか。key が ‘number’ 型ではないというエラーなのに、本体を ‘any’ にしてどうでもよくしてエラーを回避する手法ということか。なんだこのいい加減さは。たしかにエラーは回避できる。そしてこれには、注意書きがある。「It’s not the best solution but it works and is straightforward. 最善の解決法ではないけれど、機能的でわかりやすい。」と。
もう一つは、ごまかしではなく正当的な方法。こちらが本当の解答なんだと思う。それはブラケット記法の使用をやめて、Element.style.setProperty を使用する方法に変更すること。
まぁ、でも、元となるスタイルの連想配列は固定で変更もなければ、その値は保証できるので、わざわざ条件式を入れて遅くしなくとも、下のようにアサーションしてしまえば良いように思う。
あれっ!結局またアサーションなのか。で、試してみてわかったことだけれど、Element.style.setProperty を使う方法よりもブラケット記法のほうが、わずかながらではあるけれど処理が高速である。
これ、思うのだけれど、Javascript ではブラケット記法に文字列のプロパティ名を指定して当たり前の如くできていたことが、TypeScript の都合によってコンパイルエラーとされてしまうわけだ。Javascript 的には変な値を指定しているとか、変数の型があっていないとかというエラーでもなんでもなくて、あくまで、TypeScript の内部の都合による不具合なわけだ。それゆえ、アサーションというごまかしでもって型を <any> にしてコンパイラをだませば解決する問題という、いい加減というか、なんだかどうでもいいような問題に思えてしまうので、そんな問題なら、単純にアサーションでごまかしてしまえば良いように思うわけで。
これは自分的には、ブラケット記法を any でアサーションして使う方法が、最善の方法だと思えるわけで。
Post : 2022/05/21 14:17
Comments feed
Trackback URL : https://strix.main.jp/wp-trackback.php?p=170788