Javaを4年間実務で取り扱った私が、「プロを目指すためのTypeScript入門」(著: @uhyo 氏)
でTypeScriptを学んだ際に感じたことを書いていきます。
Javaと比較して、どう違うのかに着目して書いています。
JavaとTypeScriptは同じ静的型付け言語ということで、共通する部分は多くありますが、細かな違いがありました。
同じようにJava技術者がTypeScriptを学ぶ際の助けになればと思います。
第1章のものも投稿しています。
この記事の読み方
各節ごとに上段は本の内容
線を挟んで下段は私の所感になります。
文と式
文はセミコロンで終わる。
文には結果がなく、式には結果がある。
式が実際の操作を行う。
// 文 変数に文字列を代入した。という事実だけを示す
const str = "Hello" + "world!";
// 式 文字列を結合していて、結果がある
"Hello" + "world!"
文を組み合わせることも可能。(if文の中に通常の文など)
Javaと変わりなし。
式文
式のあとセミコロンを書く文のこと。
結果のない式。
関数呼び出しは式(結果がある)だけど、その結果は特に必要ない。みたいな時に使う。
console.logとか。
// 文だが、結果がない。
console.log("Hello,world");
Javaと変わりなし。
変数
const(再代入不可),let(再代入可),var(非推奨)のみっつがある。
基本constをつかう。
(letはいつ値が再代入されるかを意識しながらソースを読まないといけないため、リーダブルでない。本当に必要な場合のみ使う)
複数の変数宣言を,でつなげられる。けど変更容易性の観点からあまり使われない。
const str1 = "a",
str2 = "b",
str3 = "c"
大きくJavaと違うところ。
constを使うことで、Javaでいうfinal修飾子をつけた状態にできるので、簡潔になって良い。
型を指定せずに変数を宣言ができるので、スコープが小さくて型が明らかなものはそのままでもいいのかもしれない。
識別子
変数名として使えるルールを満たした名前のこと
予約語だったり数字が先頭にあったりするとダメ
なお、変数名は小文字 型名は大文字から始まるのが慣例
Javaと変わりなし。
予約語は全く違うので、留意しないといけない。
ただ、適切に命名していたら引っかかることはあまりないので、IDEに怒られたら変える。くらいで良いか。
型注釈
変数宣言時に型を明記できる
const 変数: 型 = 式
Javaだと型は必ず定義するものなので、してもよい。というのは新鮮。
また、Stringではなくstringであるのも注意。
tsの文字列型はプリミティブなのか、、
こんな感じで対応してるか。
// java
final String text = "hello"
// ts
const text : string = "hello"
プリミティブ型
TypeScriptにおけるプリミティブ型は以下
- 文字列(string)
- 数値(number)
- 整数・小数の区別はない
- 真偽値(boolean)
- BigInt
- でっかい整数用
- null
- 代入すべき値が存在しないため、値がない(明示的)
- undefined
- 値が代入されていないため、値がない
- シンボル ※解説対象外
また、nullは明示的に使わない限り発生しないため、存在しない要素にアクセスするなどした場合は基本undefinedになる。
Javaと比べると、文字列型はプリミティブであることや
数値型のバリエーションが少ないことが特徴か。
JavaではNullに対して忌避感を強く持っていたが、TSではundefinedに気をつけていかねば。
むしろ、Nullを本来の用途として正しく使えるか。
暗黙の型変換
文字列と結合させた数値は文字列として暗黙の型変換がされる
console.log("1234" + 1000);
// -> "12341000"
Javaと変わりなし。
明示的な型変換を行う(数値型への変換)
数値が入力された文字列型を数値型にする
console.log(Number("1234") + 1000);
// -> 2234
変換できない場合NaN
という値が返される。
また、文字列型以外も数値型にキャストできる
console.log(Number(true));
// -> 1
console.log(Number(false));
// -> 0
console.log(Number(null));
// -> 0
console.log(Number(undefined));
// -> NaN
もう一つの数値を扱う型であるBigIntにもキャストは可能。
そちらはNaNの概念がなく、変換に失敗するとランタイムエラーが発生する。
Javaでいうキャスト機能。Number型のスタティック変数と思っていい?
Javaだと変換に失敗するとランタイムエラーになるが……。
tsの場合、NaNになった場合の判定を用意する必要があるか。
Javaでは真偽値のキャストはできなかった。
また、Nullをキャストするという概念がそもそも思い浮かばなかった。。(おそらく、ほぼ使わないとは思う)
明示的な型変換を行う(その他型への変換)
同じように、以下のようなキャストも可能
console.log(String(1111));
// -> 1111
console.log(Boolean(0));
// -> false
真偽値への型変換については以下のようになる
- 数値
- 0とNaNはfalse
- それ以外はtrue
- BigInt
- 0nがfalse
- それ以外はtrue
- 文字列
- 空文字列("")がfalse
- それ以外はtrue
- null,undefined
- false
- オブジェクト
- true
上記利用して、簡潔に条件分岐を書くことができる。
// なんらかの文字列を返す関数fugaがあったとして……
const str = fuga();
if(!str){
// 空文字やnull・undefinedならこの分岐に入る。
}
isEmpty()
みたいな関数呼び出しや== null
のような式を挟まずとも真偽値にキャストされるので簡潔にかける。
こういった分岐は頻出で、Javaに慣れた人からすると違和感がかなりあって最初は面食らう。
演算子
式を作るために用いられる記号
2項演算子
二つの式をつなげる演算子
-
+
:加算 -
-
:減算 -
*
:乗算 -
/
:除算 -
%
:剰余 -
**
:べき乗
Javaにはべき乗の演算子はない。
単項演算子
一つの式に付与される演算子
-
+
: 正の値であることを示す -
-
: 負の値であることを示す -
++
: 値をインクリメントする -
--
: 値をデクリメントする
Javaと変わりなし。
文字列結合も+演算子で行う
console.log("Hello" + " World");
// -> Hello World
Javaと変わりなし。
比較演算子と等価演算子
比較演算子は二つの値を比較して真偽値を返す演算子
-
<
: 右が左より大きい -
>
: 右が左より小さい -
<=
: 右が左以上 -
>=
: 右が左以下
等価演算子は値が一致するかを判定して真偽値を返す演算子
-
==
: 等価(暗黙の型変換を含んで判定) -
!=
: 非等価(暗黙の型変換を含んで判定) -
===
: 等価 -
!==
: 非等価
基本的には===
か!==
を使う。その方が厳密に判定が可能。
ただ、nullまたはundefinedである。という判定をする場合のみ、簡潔に書くために使われる。
if(x == null){
// nullかundefinedの場合この分岐
}
JavaではString型はequals()を使わないと正しく判定できなかったりしたが、すべての値を===で比較できるのはシンプルで良い。
値を比較するのか参照先を比較するのか、といった所は考えなくても良いみたいだ。
論理演算子
真偽値を演算するために使う演算子
演算子を挟む2つの真偽値を演算する。
-
&&
: and -
||
: or -
!
: not
これはJavaと変わりなし。
真偽値以外にも論理演算子が使える
左のオペランドの判定結果によって、返却する値が分岐する
-
&&
: 左のオペランドが真の場合、右のオペランドの値を返す。 -
||
: 左のオペランドが真の場合、左のオペランドの値を返す。 -
??
: 左のオペランドがnullかundefinedの場合、右のオペランドの値を返す。
利用して、簡単にデフォルト値を扱うことができる
// 空文字("")を返す関数があったとして…
const name = getEmptyString();
const displayName = name || "名無し";
console.log(displayName);
// -> 名無し
Javaにはない書き方。見た目にびっくりするが、簡潔に書けるので便利そう。
使用するときは??
と!!
の使い分けを考えたい。
また、Java同様短絡評価もある。左のオペランドを返さない場合、左のオペランドは処理されない。
条件演算子
またの名を三項演算子とも。
条件式 ? 真の時の式 : 偽の時の式
if文を簡略化して書ける。
これはJavaと変わりなし。
Javaと同じように、この書き方の場合は記述が複雑にならないように気を付けたほうがよさそう。
基本的な制御構文
if文
以下のような構文で記述する
// 最も基本的な構文
if(条件式) 文
// 文をブロックで書く場合
if(条件式){
文
}
スタイルの統一や見た目のわかりやすさから、常にブロックと併用することも珍しくない。
基本的にはJavaと同じ使い方だけど、if文の条件式に指定された値は真偽値に自動変換されるのが特徴
const str = "a";
if(str){
// 空文字以外はtrueなのでこの分岐に入る
}
また、elseやelse if もJavaと同様に使える
switch文
以下のような構文で記述する
switch(式) {
case 式 :
文
case 式 :
文
case 式 :
文
}
switch(式)
とcase 式
の式部分の結果が一致する場合にそれ以下の文が実行される。
これはJavaと変わりなし。
なお、一致判定は===
演算子と同様のチェックが行われる。
while文
具体例を以下に示す
let sum = 0;
let i = 0;
// カウンタが100を超えるまでループし、それまでの数を合計する
while(i <= 100){
sum += i;
i++;
}
console.log(sum)
// => 5050
while後の条件式がtrueであるかぎりループを繰り返す。
また、break
で強制ループ終了
continue
で以後の処理を行わずに次のループに突入する。
これはJavaと変わりなし。
数少ないletの使えるケースになり得る。
for文
以下のような構文で記述する
for (let 変数名 = 初期化式; 条件式; 更新式) 文
// 文の部分をブロックで書くことのほうが多い
for (let 変数名 = 初期化式; 条件式; 更新式){
文
}
条件式がtrueであるかぎりループを繰り返す。
whileのほうが応用範囲が広いものの、forで事足りる局面が実際にはかなり多いため、こちらが使われることのほうが多い。
これはJavaと変わりなし。
Javaでいう拡張for文はまた3章で紹介されるようだ。
あとがき
基本的な構文はJavaとかなり近く、馴染みやすくて良いなと思いました。
ただ、真偽値周りのロジックは今までとは違う部分も多く、慣れが必要だなとも思いました。
また、第3章編も投稿しますのでそちらもぜひご一読お願いします。
参考文献