はじめに
本稿は拓殖大学ディジタルコンテンツ研究愛好会 Advent Calendar 2020 2日目の記事です。
(このまま順調に卒業資格を得られればですが)大学生最後のアドベントカレンダーになります。
TypeScriptを実際に書き始めて、JavaScriptではなくTypeScriptを選ぶ気持ちと理由を理解することが出来ました。
そこで、後輩にもTypeScriptを触ってほしいという気持ちを込めて、この場を借りて言語への入りとなるよう本稿を残します。
TypeScriptについてはTypeScript の概要を参考にしてほしいです。
よろしくお願いします。
この記事の対象
- プログラミング言語の基礎を理解している
- DOMについて最低限の知識がある
上記にたり得ていない・自信がないという方は下記のページをお供に読んで頂ければと思います。
環境
stackblitzを使います。
sign inしたあと、下記画像のTypeScriptを選びます。
これでエディタと実行結果が表示されるページが表示されます。
完成目標
下記URIより遊べます。
https://typescript-oubejk.stackblitz.io
また、githubでコードを公開しています。
こちらはParcelで動作を確認しました。
HTML・CSS
最初から存在するindex.html
とstyle.css
に書いていきます。
下記のコードをコピペします。
<div id="main">
<h1>タイピングゲーム</h1>
<div id="frame"></div>
<input type="button" id="restart" value="restart">
</div>
#main {
margin: 10px;
padding: 0;
border: 0;
}
#frame {
width: 500px;
height: 300px;
padding: 0.5em;
font-size: 24px;
border: 5px double #000000;
word-break: break-all;
white-space: normal;
}
#restart {
margin: 10px 20px;
border-bottom: solid 3px #888;
border-radius: 20px;
width: 100px;
height: 50px;
}
index.ts
内にエラーが出ますが気にしないでください。
TypeScript
TypeScriptを書いていきます。TypeScriptでは型推論が効きますが、最初はDOM周りの型で迷いがちなのであえて明記します。
本稿では先にコード全体を載せてから、その後で解説をツラツラと書き連ねていくスタイルを取ります。
コード全体
import "./style.css";
import Question from "./question";
const qnum = 10;
function main() {
const restart: HTMLElement = document.getElementById("restart");
const frame: HTMLElement = document.getElementById("frame");
if (!restart || !frame) return;
const question: Question = new Question(qnum);
frame.textContent = question.init();
restart.addEventListener("click", () => {
frame.textContent = question.init();
});
window.addEventListener("keydown", (e: KeyboardEvent) => {
e.preventDefault();
const res: string | boolean = question.main(e.key.toUpperCase());
if (typeof res == "string") frame.textContent = res;
});
}
main();
question.ts
は存在しないので、新しいファイルを作ります。
const alphabet: string[] = [
"A", "B", "C", "D", "E", "F", "G",
"H", "I", "J", "K", "L", "M", "N",
"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
];
export default class Questions {
/** 問題数 */
numOfQuestions: number;
/** 問題文 */
question: string;
/** 現在が何問目か */
selfNumber: number;
/**
* constructor
* @param qnum 問題数
*/
constructor(qnum: number) {
this.numOfQuestions = qnum;
}
/**
* 初期化する
*/
public init(): string {
this.question = "";
this.selfNumber = 0;
for (let i = 0; i < this.numOfQuestions; i++)
this.question += alphabet[Math.round(Math.random() * 25)];
return this.question;
}
/**
* メイン処理
* @param inputKey 入力されたアルファベット
*/
public main(inputKey: string): string | boolean {
if (this.question.charAt(0) === inputKey) {
this.selfNumber++;
if (this.selfNumber < this.numOfQuestions) {
this.question = this.question.slice(1, this.question.length);
return this.question;
} else {
return "終了します\nリセットボタンを押してください";
}
}
return false;
}
}
question.ts
const alphabet: string[] = [
"A", "B", "C", "D", "E", "F", "G",
"H", "I", "J", "K", "L", "M", "N",
"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
];
string型の配列を定義しています。
TypeScriptでは型をコロンの後に書きます。また、配列はType[]
とします。
export default
「そのファイルがimport
されるときになにを呼び出すか」をexport
によって定めます。
詳しくはTypescript学習メモ①(ExportとImport、require("xxx")とmodule.exports)を見てください。
numOfQuestions: number;
question: string;
selfNumber: number;
class変数です。
public
やprivate
、protected
といった修飾子を付けることも出来ます。
TypeScript日本語ドキュメントをみると分かりやすいです。
余談ですが、getterとsetterは下記のように書けます。
// getter
public get value(): T {
return val;
}
// 使い方
const res = cls.value;
// setter
public set value(v: T) {
this.val = v;
}
// 使い方
cls.value = v;
for (let i = 0; i < this.numOfQuestions; i++)
this.question += alphabet[Math.round(Math.random() * 25)];
問題数分ランダムにアルファベットを生成します。
public main(inputKey: string): string | boolean {
if (this.question.charAt(0) === inputKey) {
this.selfNumber++;
if (this.selfNumber < this.numOfQuestions) {
this.question = this.question.slice(1, this.question.length);
return this.question;
} else {
return "終了します\nリセットボタンを押してください";
}
}
return false;
}
入力されたキーと一文字目を比べ、等しいかつ問題がまだ残っていれば一文字削除。
残っていなければ終わる旨を返します。
そもそも一文字目と入力が違うならばfalse
を返します。
ここで注目なのが返り値がboolean
とstring
で違う点です。
public main(inputKey: string): string | boolean
から分かる通り、返り値に複数の型を宣言できます。
これは返り値に限らず変数や引数にも適応できます。
詳しくはTypeScriptの型入門を見てください。
type T = string | boolean;
といったこともできます。
参考:TypeScriptのInterfaceとTypeの比較
/** コメント */
これはJSDocに乗っ取った書き方をしています。
このように書くことで、エディタ上で変数や関数にカーソルを合わせるとコメントで書いたそれについての説明が表示されるようになります。
関数であれば、
/**
* 関数の説明
* @param 引数名 説明
* @return 返り値
*/
のような書き方ができ、外部から見てもその関数が何をする関数で何を受け取って何を返すのか型以上の情報を得ることができます。
JSDocで検索をかけてみると詳細な書き方が出てくるので、これを使って開発を楽に進めましょう。
index.ts
import "./style.css";
import Question from "./question";
ファイルをimportしています。cssファイルもimportできます。
const restart: HTMLElement = document.getElementById("restart");
const frame: HTMLElement = document.getElementById("frame");
HTMLElement型というものがあります。
これが厄介でHTML○○Elementといったものがたくさんあります。
検索をかけたりエディタの機能を使ったりして特定し、怒られないよう詰めていきます。
また、idが見つからなかった場合エラーを起こしてしまう可能性があるので、
if (!restart || !frame) return;
としておきます。
const res: string | boolean = question.main(e.key.toUpperCase());
if (typeof res == "string") frame.textContent = res;
question.main
にキー入力を大文字にしたものを渡します。
e
はKeyboardEvent
型ですが、key
はstring型を返すためtoUpperCase()
が使えます。
大文字にするのは、question.ts
から分かる通り、大文字で問題文を生成しているためです。
また、question.main
はstringとbooleanを返すのにframe.textContent = res
が出来るのはif (typeof res == "string")
にて型を絞っているためです。
また、TypeScriptには型アサーションという機能があります。
"!"(エクスクラメーション/感嘆符)といったものもあります。
main();
Parcelなどを使うときはwindow.addEventListener("load", main);
にしますが今回はこれでOK。
おわりに
月並みではありますが、TypeScriptはJavaScriptとは違い、(DOM周りを書いていると型が煩わしくなるときがありますが)型があることやnull安全であることがやはり強力です。またコードを書いている段階でエディタ側が間違いを指摘してくれるのも大きな強みです。
是非これを機会にTypeScriptで遊んでもらえればと思います。
以上、拓殖大学ディジタルコンテンツ研究愛好会 Advent Calendar 2020 2日目でした。