🦄
Object.keys() が string[] を返してくる問題を解決する3つの方法
Object.keys() が string[] を返してくる問題
Object.keys()
で取り出した配列の型は string[]
なので
別の配列型に割り当てるとエラーになります
const FRUITS = {
'PEACH': 'モモ',
'BANANA': 'バナナ',
} as const
type Fruits = keyof typeof FRUITS // "PEACH" | "BANANA"
const fruits: Fruits[] = Object.keys(FRUITS) // エラー
// Type 'string[]' is not assignable to type '("PEACH" | "BANANA")[]'.
// Type 'string' is not assignable to type '"PEACH" | "BANANA"'.
解決策1: Type Assertion(型アサーション)を使う
一番手っ取り早い方法です
Type Assertion(型アサーション)を使うことに抵抗がなければ、これで良いと思います
const fruits: Fruits[] = Object.keys(FRUITS) as Fruits[]
console.log(fruits) // ['PEACH', 'BANANA']
Type Assertion(型アサーション)は強力すぎる
人は誰しも間違うものです
もしも、間違った型で上書きしていたら、どうなるでしょうか?
例えば……
以下のようなに Fruits
に加えて Animal
の型が増えたとします
const FRUITS = {
'PEACH': 'モモ',
'BANANA': 'バナナ',
} as const
const ANIMAL = {
'DOG': 'いぬ',
'CAT': 'ねこ',
} as const
type Fruits = keyof typeof FRUITS // "PEACH" | "BANANA"
type Animal = keyof typeof ANIMAL // "DOG" | "CAT"
与える引数を間違えて書いた場合
以下のように Object.keys()
に与える引数を間違えて書いてしまうと
配列の値と型が異なり、危険な状態となります
const animals: Animal[] = Object.keys(FRUITS) as Animal[]
console.log(animals) // ['PEACH', 'BANANA'] 危険!
解決策2: ラッパー関数を作り型で縛る
const keys = <T extends {}, K extends keyof T>(o: T) => Object.keys(o) as K[]
const fruits: Fruits[] = keys(FRUITS)
console.log(fruits) // ['PEACH', 'BANANA']
解決策1よりもまだましな点として
K extends keyof T
によって返り値の配列型 K[]
が
引数のオブジェクト型 T
のプロパティである確認をしています
解決策1の問題は解決されたのか?
与える引数を間違えて書いた場合
解決策1と同じように、ラッパー関数に与える引数を間違えて書いてみます
const animals: Animal[] = keys(FRUITS) // エラー
// Type '("PEACH" | "BANANA")[]' is not assignable to type '("DOG" | "CAT")[]'.
// Type '"PEACH" | "BANANA"' is not assignable to type '"DOG" | "CAT"'.
// Type '"PEACH"' is not assignable to type '"DOG" | "CAT"'.
解決策2は、エラーになるので安全です
解決策3: ユーザー定義型ガードを filter 関数に与える
宗教的な理由により、どうしてもType Assertion(型アサーション)を使いたくない場合に使います
const fruits: Fruits[] = Object.keys(FRUITS)
.filter((key): key is keyof typeof FRUITS => key in FRUITS)
console.log(fruits) // ['PEACH', 'BANANA']
filter
関数にユーザー定義型ガードを与え
key in FRUITS
で、オブジェクトのプロパティである確認をして
型述語 key is keyof typeof FRUITS
で、返り値の型を絞り込んでいます
解決策3は安全なのか?
与える引数を間違えて書いた場合
// Object.keys()に与える引数を間違えている
const animals1: Animal[] = Object.keys(FRUITS) // エラー
.filter((key): key is keyof typeof FRUITS => key in FRUITS)
// Type '("PEACH" | "BANANA")[]' is not assignable to type '("DOG" | "CAT")[]'.
// Object.keys()に与える引数を間違えている、is keyof typeofで使う定数が間違っている
const animals2: Animal[] = Object.keys(FRUITS) // エラー
.filter((key): key is keyof typeof FRUITS => key in ANIMAL)
// Type '("PEACH" | "BANANA")[]' is not assignable to type '("DOG" | "CAT")[]'.
// Object.keys()に与える引数を間違えている、key inで使う定数が間違っている
const animals3: Animal[] = Object.keys(FRUITS)
.filter((key): key is keyof typeof ANIMAL => key in FRUITS)
console.log(animals3) // ['PEACH', 'BANANA'] 危険!
// Object.keys()に与える引数を間違えている、filter評価関数で使う定数が間違っている
const animals4: Animal[] = Object.keys(FRUITS)
.filter((key): key is keyof typeof ANIMAL => key in ANIMAL)
console.log(animals4) // []
filter
関数に与えるユーザー定義型ガードの書き方次第で危険な場合もあります
間違えて書く可能性が増えているのでは?
はい
ラッパー関数化
すみません、上手く書けませんでした……
結論どれを使うと良いのか?
絶対に間違えない人は、解決策1で良いと思います
試してみる
Discussion