LiteralTypes の Widening / NonWidening -
let / const で明示的型キャストを付与せずに宣言すると以下のとおりとなる。
const ONE = 1 // ONE: 1
let TWO = 2 // TWO: number
const によるプリミティブの暗黙的型キャストは LiteralTypes となるが、**これが普通の LiteralTypes ではない。**以下では Numeric で解説するが、LiteralTypes において当てはまる。Annotation / Assertion を付与すると NonWidening
となり、明示的型キャストがない場合 Widening
となる。これは VSCode などの見た目上では気づかない。
const THREE = 3 // THREE: 3 (Widening NumericLiteralTypes)
const FOUR: 4 = 4 // FOUR: 4 (NonWidening NumericLiteralTypes)
const FIVE = 5 as 5 // FIVE: 5 (NonWidening NumericLiteralTypes)
WideningLiteralType について
TypeScript2.1にひっそりと書かれていることと、よく参照されるissueにまとまっている。LiteralTypes と双方互換がある別型と認識した方が良さそう。
const c1 = cond ? "foo" : "bar"; // widening "foo" | widening "bar"
const c2: "foo" | "bar" = c1; // "foo" | "bar"
const c3 = cond ? c1 : c2; // "foo" | "bar"
const c4 = cond ? c3 : "baz"; // "foo" | "bar" | widening "baz"
const c5: "foo" | "bar" | "baz" = c4; // "foo" | "bar" | "baz"
let v1 = c1; // string
let v2 = c2; // "foo" | "bar"
let v3 = c3; // "foo" | "bar"
let v4 = c4; // string
let v5 = c5; // "foo" | "bar" | "baz"
挙動の違い
冒頭の通り、推論結果の見た目上では Widening / NonWidening は判別できない。
const INCREMENT = "INCREMENT"
const SET_COUNT = "SET_COUNT" as "SET_COUNT"
/** 【推論結果】 **/
const INCREMENT: "INCREMENT"
const SET_COUNT: "SET_COUNT"
WideningStringLiteral は object に代入することで、string型になる。
const object = {
INCREMENT, // (Widening)
SET_COUNT, // (NonWidening)
}
/** 【推論結果】 **/
const object: {
INCREMENT: string;
SET_COUNT: "SET_COUNT";
}
WideningLiteral instance に対し、typeof WideningLiteral instance で Assertion を付与すると、NonWideningLiteral になる。
const object = {
INCREMENT: INCREMENT as (typeof INCREMENT),
SET_COUNT
}
/** 【推論結果】 **/
const object: {
INCREMENT: "INCREMENT";
SET_COUNT: "SET_COUNT";
}
const は再代入不可であり、object は再代入可能という性質の折衷案と察した。widening に気づきやすい環境に期待。