起こった問題
開発中に以下のエラーが出た。
おそらく初めての遭遇。
Uncaught TypeError: Invalid attempt to destructure non-iterable instance.
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.
翻訳しながら解説します
キャッチできなかったタイプエラー: 反復可能オブジェクトは分割代入できません。
できるようにしたいのなら、 [Symbol.iterator]() メソッドを持つ必要があります。
という感じです。
destructure
は、分割代入 (Destructuring assignment) のことですね。
iterable instance
とは 反復可能オブジェクトのことだと思います。
反復可能オブジェクトはfor...of文
が使えるオブジェクトのことだと思います。
反復が可能なオブジェクトのことを「反復可能オブジェクト(iterable instance おそらく iterable object)」と言うのでしょう。
String, Array, TypedArray, Map, Set は、すべての組み込み反復可能オブジェクトです。
ちなみにイテレータとは、繰り返しのための機構です。
[Symbol.iterator]() メソッドを持つ必要があります。
に関しては後述します。
今回踏んだエラーの概要(原因)
こんなのを作ってました。
export const Component = ({ props }) => {
const { test1, test2 } = props
const [mainData, subData] = makeData({ test1, test2 })
return (
<>
<span>{mainData}</span>
<small>{subData}</small>
</>
)
}
問題はここから
関数 makeData
の戻り値は null | string | Array<string>
だったのです(ごめん)。
Array<string>
ならいいんですが、null
は、反復可能オブジェクトじゃないのでエラーが出たのだと思います。
解決方法
関数 makeData()
の戻り値を反復可能オブジェクトに限定すればエラーは解消されます。
文字列も配列に分割できる。
ちなみに、文字列も反復が可能なオブジェクトになるため、分割代入が可能ですし、スプレッド演算子も使用可能です。
const [test1, test2] = "abcdef";
console.log(test1);
console.log(test2);
// => a
// => b
const test = "abcdef";
const arr = [...test]
console.log(arr);
// output => ["a", "b", "c", "d", "e", "f"]
[Symbol.iterator]()
メソッドを持つ必要があるとはどういうことか?
後述します。の続きです。
ちなみに、こんな記述をしても上記のようなエラー文は出力されます。
const obj = {
key1: "value1",
key2: "value2",
key3: "value3"
};
for (let item of obj) {
console.log(item);
}
デフォルトでは、 {}
オブジェクトは反復可能ではありません。
もちろん、配列は反復可能(オブジェクト)です。
なのでObject.keys(obj)
すれば配列(反復可能オブジェクト)に変換されます。
const obj = {
key1: "value1",
key2: "value2",
key3: "value3"
};
const items = Object.keys(obj)
for (let item of items) {
console.log(item);
}
// output
// key1
// key2
// key3
反復可能オブジェクトを自作すると、より明確に分かるかもしれません。
やり方
ひとこと
Objectのプロトタイプに格納されている[Symbol.iterator]
プロパティにイテレータを返す関数で書く
手順
- Objectのプロトタイプに対して、イテレータを返す関数を登録する
- お仕事ではプロトタイプの変更はやったらだめ
- 理由:不要なバグの温床になるので
- 関数内にはnextメソッドを登録する(イテレータの条件)
- nextメソッドのリターンは
-
value
: numberとdone
: booleanが必要
-
- nextメソッドのリターンは
- お仕事ではプロトタイプの変更はやったらだめ
const obj = {
key1: "value1",
key2: "value2",
key3: "value3"
};
Object.prototype[Symbol.iterator] = function () {
const keys = Object.keys(this); // [key1, key2, key3]
let i = 0; // 初期化
let _this = this; // next(){} 内でのthisはnext()になってしまうので定義
return {
next() {
let key = key[i++]; // key[i]を返してから +1する
return {
value: [key, _this[key]], // thisは実行するオブジェクトの位置によって参照先が変わる。なのでスコープチェーンを辿ってレキシカルスコープに辿って_thisを参照している
done: i > keys.length
};
}
};
};
// これでただのオブジェクトが反復可能になります
for (let item of obj) {
console.log(item);
}
すごく無理やりな実装ですが、これで晴れて non-array objects
に対して、[Symbol.iterator]()
メソッドがhaveされたことになりました。
つまり、オブジェクトが、反復可能オブジェクトになったということです。
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.
ただし、再度言いますが、お仕事ではバグの温床になるのでこんな書き方はしないほうがいいと思います。
あくまでも理解を深めるための勉強に留めておきましょう。
参考
アウトプット100本ノック実施中