TypeScript で innerText の ts2339 エラーを対処する
TypeScript で HTML DOM 操作していた際に innerText プロパティを参照したところ ts2339 エラーが発生したので、原因と対処方法について紹介します。
やりたいこと
HTML 内の <span class="data"> 要素を全部取ってきて、いくつかの要素内のテキストを取得したかったので、以下のようなコードを TypeScript で書きました。
const spans = document.querySelectorAll('span.data');
const data1 = spans[0].innerText;
const data2 = spans[5].innerText;ところが、Property 'innerText' does not exist on type 'Element'. (ts2339) と TypeScript のエラーがでます。Element 型に innerText というプロパティはないといわれています。
このエラーの原因の調査と、対処方法について紹介します。
環境
- ブラウザ
- Microsoft Edge 121.0.2277.112
- typescript
- 5.2.2
HTML DOM API の仕様
TypeScript の前に、まず HTML DOM API 自体の仕様を確認しようと思い、MDN で innerText を調べます。
innerTextはHTMLElementのプロパティで、ノードとその子孫の「レンダリングされている」テキスト内容を示します。
なるほど、innerText は HTMLElement のプロパティなので、HTMLElement の継承元である Element は確かに innerText プロパティはないわけですね。
ここでふと、そもそも querySelectorAll() が返す型ってなんだっけと思い、こちらも調べます。
生きていない
NodeListで、指定されたセレクターの 1 つ以上に一致する子孫ノード 1 つに対して 1 つずつのElementを含みます。
予想通りですが、Element の NodeList でした。TypeScript のエラーが言っていることは正しいわけですね…。
なお、蛇足ですが、innerText は HTMLElement のプロパティでしたが、innerHTML は Element のプロパティなんですね。知りませんでした…。
ElementオブジェクトのinnerHTMLプロパティは、要素内の HTML または XML のマークアップを取得したり設定したりします。
TypeScript の仕様
原因はほぼわかったようなものですが、念のため TypeScript の仕様も確認してみました。
querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]>; querySelectorAll<K extends keyof SVGElementTagNameMap>(selectors: K): NodeListOf<SVGElementTagNameMap[K]>; querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>;
驚きだったのが、私は 3 つ目の仕様のみを期待していたのですが、getElementsByTagName のような使い方で、引数に HTML や SVG のタグ名のみを渡す場合はそのタグに応じた型の NodeListOf で返してくれるようです。
実際に試してみましたが、以下の元のコードでは spans は NodeListOf<Element> 型でしたが、
const spans = document.querySelectorAll('span.data');
const data1 = spans[0].innerText;
const data2 = spans[5].innerText;以下のように querySelectorAll() の引数をタグ名にしてみると、spans は NodeListOf<HTMLSpanElement> 型に型推論され、HTMLSpanElement は HTMLElement を継承しているので、innerText プロパティを参照しても冒頭の ts2339 エラーはでなくなりました。
const spans = document.querySelectorAll('span');
const data1 = spans[0].innerText;
const data2 = spans[5].innerText;型アサーションで対応
さて、TypeScript で querySelectorAll() の引数でタグ名を指定した場合は、そのタグに応じた型に型推論がなされることはわかりましたが、今回はタグ名ではなく、クラス名を含めたセレクタ文字列を渡したかったので、型アサーションで ts2339 エラーを回避したいと思います。
const spans = <NodeListOf<HTMLSpanElement>>document.querySelectorAll('span.data');
const data1 = spans[0].innerText;
const data2 = spans[5].innerText;または
const spans = document.querySelectorAll('span.data') as NodeListOf<HTMLSpanElement>;
const data1 = spans[0].innerText;
const data2 = spans[5].innerText;と型アサーションをつけることで ts2339 エラーはでなくなります。
なお、innerText プロパティは HTMLElement のプロパティなので、厳密に NodeListOf<HTMLSpanElement> としなくても NodeListOf<HTMLElement> でも問題ありません。
また今回セレクタの全要素が欲しかったので querySelectorAll() を使用していますが querySelector() の場合は、
const span = <HTMLSpanElement>document.querySelector('span.data');または、
const span = document.querySelector('span.data') as HTMLSpanElement;のように型アサーションします。
あとがき
TypeScript で 直に HTML DOM 操作することってそれほど多くないと思うのですが、たまたま実装する機会があって、結果勉強になりました。