TypeScriptのWidening・Narrowingについて
TypeScriptの練習問題を解いている最中、 Widening
とNarrowing
という概念があったので、それぞれについて整理したいと思います。
Widening
Widening
とは、英語で「広がる」という意味を表す widen
の現在分詞であり、「拡大」のことを指します。
それでは、型の拡大とはどのような意味なんでしょう?
TSでlet
とconst
で変数を定義し、それぞれ値を代入した場合の4パターンの変数の型はこのようになります。
// パターン1: let => let
let value1 = "widening"; // let value1: string
let clone1 = value1; // let clone1: string
// パターン2: let => const
let value2 = "widening"; // let value2: string
const clone2 = value2; // const clone2: string
// パターン3: const => let
const value3 = "widening"; // const value3: "widenging"
let clone3 = value3; // let clone3: string
// パターン4: const => const
const value4 = "widening"; // const value4: "widenging"
const clone4 = value4; // const clone4: "widenging"
この時、パターン3のconst
の値をlet
の値に代入した例を見てみましょう。
const
で定義されたvalue3
は"widening"型
で定義されていたにも関わらず、let
で定義されたclone3
はstring型
で定義されてしまっています。
そのため、clone3
は適当な文字列で値を上書きできてしまいます。
// パターン3: const => let
const value3 = "widening"; // const value3: "widenging"
let clone3 = value3; // let clone3: string
clone3 = "hoge"; // ok
このように、"widening"型
からstring型
になってしまう事象をWidening
(型の拡大)と言います。
では、Wideningの解決策は何かというと、変数に対してas const
を付与すると、型が拡大せずに済みます。
// パターン3: const => let
const value3 = "widening" as const; // const value3: "widenging"
let clone3 = value3; // let clone3: "widening"
clone3 = "hoge"; // Type '"hoge"' is not assignable to type '"widening"'.
具体的に、どのような場合に気をつけるべきかというと、
定義した変数をexport
で参照できるようにした場合、値が変更できてしまう恐れがあるため対策しておきましょう。
Badパターン
export const ROLES = {
admin: "ADMIN",
readonly: "READ_ONLY",
}
import { ROLES } from 'constants.ts';
ROLES.readonly = "mutable";
console.log(ROLES);
// { admin: "ADMIN", readonly: "mutable" }
Goodパターン
export const ROLES = {
admin: "ADMIN",
readonly: "READ_ONLY",
} as const
import { ROLES } from 'constants.ts';
ROLES.readonly = "mutable";
// Cannot assign to 'readonly' because it is a read-only property.
たしかに、最近アサインしたチームのコードを見た時に
毎回、as const
をしていたのは、このためだったんですね!
Narrowing
Narrowing
とは、Widening
の対義語であり、英語で「狭い」という意味を表すnarrow
の現在分詞です。
「型を狭くする」という意味は、ある程度想像できるかと思います。
入力値の先頭に空白・文字列を追加する関数があり、引数のpadding
がnumber
とstring
を許容している場合、下記のコードだとエラーになってしまいます。
const padLeft = (padding: number | string, input: string): string => {
return " ".repeat(padding) + input;
// Argument of type 'string | number' is not assignable to parameter of type 'number'.
// Type 'string' is not assignable to type 'number'.
}
なので、一般的に型判定の条件分岐を追加するかと思います。
この処理のことをType Narrowing
と言います。
const padLeft = (padding: number | string, input: string): string => {
if (typeof padding === "number") {
return " ".repeat(padding) + input;
}
return padding + input;
}
Type Narrowingは、今までもごく普通に使ってましたね😁
公式サイトには、他にもいくつかの方法が紹介されていたので、参考になるかもしれないです。
Discussion