「再代入時の型チェック」とは何をしているのか
TypeScriptは「一度決めた型に対して、その後もずっと一貫しているか」をコンパイル時にチェックします。
変数に最初に値を入れた瞬間、あるいは型注釈を書いた瞬間に、その変数の「型」が決まります。
そのあと再代入するときに、「その型と矛盾していないか?」を毎回チェックしている——これが再代入時の型チェックです。
let count: number = 1;
count = 2; // OK
count = "3"; // エラー:string は number に代入できない
TypeScriptここで TypeScript は、「count は number だと宣言されているから、number 以外は入れちゃダメ」と判断してエラーにします。
この「型に従って正しく実行できるようにする」性質が、いわゆる型安全性です。
明示型の場合の再代入チェック
変数に型注釈を書いたとき
型注釈を付けた変数は、その型でロックされます。
let name: string = "Taro";
name = "Hanako"; // OK(string → string)
name = "123"; // OK(中身は数字でも型は string)
// name = 123; // エラー(number は string に代入できない)
TypeScriptここで重要なのは、「見た目が数字かどうか」ではなく、「型として string か number か」です。
TypeScript は「型が一致しているか」だけを見ていて、「なんとなく変換できそうだからOK」とはしません。
ユニオン型の場合
ユニオン型(string | null など)の場合も、「その型のどれかに当てはまるか」をチェックします。
let currentUser: string | null = null;
currentUser = "taro"; // OK(string)
currentUser = null; // OK(null)
// currentUser = 123; // エラー(number は string | null に含まれない)
TypeScript「この変数は string か null のどちらかしか取らない」という約束を、再代入のたびに守らせているイメージです。
型推論された変数の再代入チェック
初期値から決まった型を守らせる
型注釈を書かなくても、初期値から推論された型に対して同じようにチェックが入ります。
let score = 80; // number と推論される
score = 100; // OK
// score = "100"; // エラー:string は number に代入できない
TypeScriptlet score = 80; と書いた瞬間に、「score は number」と決まります。
その後の再代入でも、「number 以外はダメ」というルールが適用され続けます。
const title = "入門 TypeScript"; // string と推論
// title = "改訂版"; // そもそも const なので再代入自体がエラー
TypeScriptconst の場合は「再代入禁止」なので、型チェック以前に「代入そのものがダメ」です。
オブジェクト・配列の再代入とプロパティの型チェック
オブジェクトのプロパティ再代入
オブジェクトに型が付いている場合、そのプロパティに対しても型チェックが働きます。
type User = {
id: number;
name: string;
};
let user: User = {
id: 1,
name: "Taro"
};
user.name = "Hanako"; // OK(string)
// user.id = "2"; // エラー(string は number に代入できない)
TypeScriptuser という変数自体の型は User で固定され、その中の id は number、name は string と決まっています。
再代入時には、「そのプロパティの型と合っているか」が毎回チェックされます。
配列の要素再代入
配列も同様に、「この配列は何の配列か」が決まったあと、その要素の再代入がチェックされます。
let scores: number[] = [80, 90];
scores[0] = 100; // OK
// scores[1] = "0"; // エラー(string は number に代入できない)
scores.push(70); // OK
// scores.push("70"); // エラー
TypeScriptnumber[] と決まった時点で、「この配列には number しか入れない」という契約ができていて、push やインデックス代入のたびにそれが守られているか確認されます。
any が混ざると再代入チェックが崩れる
any は「型チェックの穴」
any 型は、「何でも入るし、どこにでも代入できる」特別な型です。
便利な反面、再代入時の型チェックをすり抜けてしまう危険な存在でもあります。
let value: any = 1;
value = "hello"; // OK(any なので何でも入る)
let num: number = 10;
num = value; // コンパイル上はOK(any → number)
TypeScriptここで value が実際には "hello" でも、コンパイラは止められません。
実行時に num * 2 のような計算をすると、「数値のつもりが文字列だった」というバグにつながります。
だからこそ、ESLint などでは「any を安易に代入するな」というルールが用意されていたりします。
再代入時の型チェックをちゃんと効かせるためにも、any は極力使わない・閉じ込める、という意識が大事です。
「再代入時の型チェック」が守ってくれているもの
TypeScriptの再代入チェックは、ざっくり言うとこういう事故を防いでくれています。
「最初は数値だった変数に、途中で文字列を入れてしまう」
「null を許していない変数に、うっかり null を入れてしまう」
「User 型のつもりのオブジェクトに、違う形のデータを突っ込んでしまう」
一度決めた型に対して、再代入のたびに「それ、本当にその型?」と問い続けてくれる。
そのおかげで、「型のズレ」が実行前に見つかり、型安全性が保たれるわけです。
あなたがやるべきことはシンプルで、
「最初に正しい型を決める(明示型 or 推論)」
「any に逃げない」
この2つを意識しておけば、再代入時の型チェックはかなり強力な味方になってくれます。
