はじめに
Progaku Advent Calendar 2023
25日目の投稿になります。
フロントエンド学習記No.2です!
今回は前回JavaScriptで作成したアプリをTypeScriptで書き換えながら学んでいきます。
静的型付けによる安全性、バグ検知の利点を初学者なりに少しでも実感することが目標です!
前回の記事はこちら
環境構築
こちらの記事を参考に行いました。
tsc = TypeScriptのコンパイラをターミナル上で実行し、jsファイルを生成して動かしていきます。
TypeSriptで書いてみる
早速、型チェックによるたくさんのエラー、警告が出ていますね。
一つずつ見ていきます。
非アサーション演算子でnullでないことを保証する
function updateMeter(counterValue) {
const meter = document.querySelector('#meter');
const maxWidth = 200;
const newWidth = (maxWidth / 10) * counterValue;
meter.setAttribute('width', newWidth);
}
meterはnullの可能性がありますよ、と言っています。
これはquerySelectorはelement型とnullを返すためのようです。
その上、meterが存在しない場合の処理も書いていないので怒られているわけですね。
そういった時に値がnullでないことを証明する非アサーション演算子というものがTypeScriptにはあるようです。
以下参考記事
今回で言うとsvg要素であるmeterは確実に存在するので使っていきます。
const meter = document.querySelector('#meter')! as SVGElement;
末尾に!をつけるだけで使用できます。
そして型推論でElement型と認識してくれていましたが、せっかくなので明示的に型も定義していきます
SVGの要素はSVGElement型のようです。
CountUpButton
CountDownButton
こちらも同様にnulをとらず確実に存在することを示すため非アサーション演算子を適用していきます。
const countUpButton = document.querySelector('#countup')! as HTMLButtonElement;
const countDownButton = document.querySelector('#countdown')! as HTMLButtonElement;
HTMLの属性の値は文字列型と知る
次が、個人的に驚いたのですが(無知なだけですが笑)
meter.setAttribute('width', newWidth);
ここですね。
meterのwidth属性に現在のカウントに対応するwidthを反映する処理なのですがnumber型であるnewWidthをstringのパラメーターに入れることはできないと言っています。
調べるとHTMLの属性は、文字列型なんですね。
widthなどは数値型と勝手に思い込んでいました。
なのでsetAttributeは第1引数に属性名、第2引数に文字列型をとるわけですね。
そして普通にmdnにも書いていましたね・・・
ちゃんと読んでいなくてごめんなさい・・・
文字列以外をとった時は自動的に変換してくれていたようです。
暗黙的に文字列に変換してくれていたのは他にあるわけでして
レベルの値の加算箇所
levelElement.textContent = levelValue + 1;
メータの値をセットする箇所
counterElement.textContent = currentValue;
文字列を取るtextContentにしっかりと数値型を入れてしまっていましたね・・
テンプレートリテラルで文字列にして修正します。
levelElement.textContent = `${levelValue + 1}`;
counterElement.textContent = `${currentValue}`;
最後に、updateConterメソッド内ですね。
counter要素とLevelCountの要素があることを保証しても、その中のtextContetがnullの可能性もあリます。
それを伝えてくれていました。
function updateCounter(amount: number) {
if (!counterElement.textContent || !levelElement.textContent){
console.error('必要な要素が存在しません。')
return
}
! = not演算子です。
値が存在しない場合にerrorを表示して処理を止めるように追加。
そうして
TypeSriptのコンパイラのご指導のもと修正したコードがこちらになります。
const INCREMENT: number = 1;
const DECREMENT: number = -1
function updateMeter(counterValue: number) {
const meter = document.querySelector('#meter')! as SVGElement;
const maxWidth = 200;
const newWidth: number = (maxWidth / 10) * counterValue;
meter.setAttribute('width', `${newWidth}`);
}
const counterElement = document.querySelector('#counter')! as HTMLParagraphElement ;
const levelElement = document.querySelector('#LevelCount')! as HTMLParagraphElement;
function updateCounter(amount: number) {
if (!counterElement.textContent || !levelElement.textContent){
console.error('必要な要素が存在しません。')
return
}
let currentValue = parseInt(counterElement.textContent);
let levelValue = parseInt(levelElement.textContent);
if (amount > 0 && currentValue < 10) {
currentValue += amount;
} else if (amount < 0 && currentValue > 0) {
currentValue += amount;
} else if (currentValue === 10) {
levelElement.textContent = `${levelValue + 1}`;
currentValue = 0;
window.alert('おめでとうございます!レベルアップしました!');
}
counterElement.textContent = `${currentValue}`;
updateMeter(currentValue)
}
const countUpButton = document.querySelector('#countup')! as HTMLButtonElement;
countUpButton.addEventListener('click', () => updateCounter(INCREMENT));
const countDownButton = document.querySelector('#countdown')! as HTMLButtonElement;
countDownButton.addEventListener('click', () => updateCounter(DECREMENT));
まとめ
TypeSriptを使ってみて、コンパイラに怒られまくることで、自身のコードの安全性のなさ、コードを書く力の低さを改めて実感しました・・・笑
ただ、コードを実行する前に、型チェック等で知らせてくれることで、そうした不備に気づくことができるというのがTypeSriptや静的型付け言語の大きなメリットの一つだと実感しました。
その特性を活かして、適切に型を定義したり、コードを書くことができていれば修正等で誤った値が入った際などにきちんとエラーが吐き出されたり、原因を特定しやすくなるのかなと。大規模なシステムで多数のエンジニアで開発する際などよりメリットが大きくなりそうですね。
その他参考記事