はじめに
JavaScriptを業務で使うようになってから、理解が曖昧だったところを学び直しています。
今回はアサーションについてです。
まず、アサーションの説明の前に、型キャストとの違いを載せておきます。
| ### | 実行タイミング | 内容 |
|---|---|---|
| 型アサーション | コンパイル時 | コンパイラに型を伝えるだけ、実行できるがどうなるかはわからん |
| 型キャスト | 実行時 | ある値の型を別の型に変換する、 |
つまりアサーションとはコンパイラにプログラマーが情報を付加している操作で実体としては変化してません。
なのでアサーションはTypeScriptの型推論の限界をプログラマーで補っているようなものです。型ガードによる絞り込み(narrowing)で解決できるなら型ガードの方が安全です。
https://www.typescriptlang.org/docs/handbook/2/everyday-types.html?utm_source=chatgpt.com#type-assertions
公式ドキュメントでは、DOM要素の型を指定したい時に使っています。
TypeScriptでのアサーションは大きく分けて型アサーションとconst アサーションの2つあります。まずは型アサーションから
型アサーション
- 「number を string」に型アサーションするなどの無茶な変換はできない
- が、unknownを経由すると回避することは可能。ただし型安全が保証されないので非推奨です。
const num = 123;
// const a = num as string エラー
const b = num as never // コンパイラから見るとbはnever型となる
const str = num as unknown as string; // OKだが非推奨
型アサーションには2つの記法があります。以下の例は無理やりアサーションを使っているので、型ガードによる絞り込みをする方が安全だと思います。
as構文: (変数 as 型名)
function getValue(): string | number {
return "Hello";
}
const value = getValue();
// console.log(value.length); エラー:numberの可能性があるため
const len = (value as string).length;
// 下の方が安全
if (typeof(value) ==='string'){
const len = value.length;
console.log(len);
}
アングルブラケット構文(angle-bracket syntax) : (<型名>変数)
書き方が違うだけでなく、アングルブラケット構文は.tsだと使えるが、JSXの構文と衝突するため.tsxでは使えないというデメリットがあります。これを見てアングルブラケット構文は読めるようにはしておいて、書くのはas構文だけにしようと思いました。
https://www.typescriptlang.org/docs/handbook/jsx.html?utm_source=chatgpt.com#the-as-operator
function getValue(): string | number {
return "Hello";
}
const value = getValue();
// console.log(value.length); エラー:numberの可能性があるため
const len = (<string>value).length;
const アサーション
const は変数への再代入を禁止するだけで、オブジェクトのプロパティの変更は許可し、
as const は型推論に影響し、オブジェクトリテラルのプロパティを readonly として扱う
ものです。
const アサーションによる変更は以下のような感じ
- プリミティブ値をリテラル型にする
- オブジェクトリテラルのプロパティを
readonlyにする-
リテラルの中で直接作ったオブジェクト は、ネスト先も
readonlyになる - 別オブジェクトを参照している場合、as const が固定しているのはそのプロパティなので、元のオブジェクトをreadonlyにしているわけではないので、書き換え可能
-
リテラルの中で直接作ったオブジェクト は、ネスト先も
- 配列リテラルを
readonly tupleにする
let hello = "hello";
let bye = "bye" as const;
// let bye: "bye" となり、リテラル型なので実質的に書き換えられないのでas const
let len = 10
const flag = 1
const user = {
name: 'test',
age: 20
}
const data = {
status: "success", // readonly status: "success";
count: 1, // readonly count: 1;
position: [10, 20], // readonly position: readonly [10, 20];
address: { // readonly address: {
city: "Tokyo", // readonly city: "Tokyo";
zip: 100, // readonly zip: 100;
}, // };
len: len, // readonly len: number;
flag, // readonly flag: 1;
user // readonly user: { name: string; age: number; };
} as const;
data.status = "fail" //エラー
data.address.city = 'Osaka'//エラー
data.len = 1 //エラー
len = 1 //元のオブジェクトまでreadonlyにはならない
user.name = 'fix' //data.userにはオブジェクトの参照が入っているのでdata.user.nameも変わる
console.log(data)
data.user.name = 'fix' //書き換え可能