LoginSignup
6
2

More than 3 years have passed since last update.

[...Array(N)].map(() => doSomething()) でのN回実行は, TypeScriptでは動作しない...場合がある

Last updated at Posted at 2019-03-29

TL;DR

TypeScriptを利用する場合, TypeScriptのバージョンとトランスパイルするtargetのバージョンに気をつけましょう.
古すぎると利用できない構文などがあります.

タイトルのスプレッド構文 [...Array(5)] に関しては, TypeScript v3.6以上を利用するか, targetにES2015以上を利用すると動くはずです.

反省

2020/02/26
流石に雑な内容の記事のまま放置しすぎたなと思い再確認することにしました.
というか記事全文書き直しました.
元々この記事は,

CodePenの Pen Settings > JSJavaScript PreprocessorTypeScript にすると,
スプレッド構文が動かない!?なんで!?

CodePenはトランスパイルのtargetにTSデフォルトの ES3 を指定するなど,
古いJSを出力するような設定になっているのかも (@cfm-art さんのコメントによる)

なるほど!(思考停止)

というあんまりな状態で放置されていました. しかもタイトルにはCodePenと入っていない...

今改めて確認すると, CodePenでTypeScriptを選択した場合でも, スプレッド構文がちゃんと使えるようになっていました.
この1年弱の間に何らかのアップデートがあったものと思われます. つまり今となっては以前の挙動を確認しようがありません. リリースノートが見たい.

具体的に今現在のCodePenでどのような設定が使われているのかはわかっていません. すみません.

検証

さて, 実際に @cfm-art さんの指摘を検証してみます.

検証環境にはTypeScript PlayGroundを利用します.
公式の環境なので, 信頼できると判断します.

現在のTypeScript PlayGroundでは, 左の入力欄にTSのコードを書くと, 右の出力欄にJSにトランスパイルされたコードが出力されます.
便利.

そしてプルダウンからは, TSのバージョンやトランスパイルに利用するtargetのバージョンを選択できます.
便利.

そしてRunを押下すると, 実際に実行することができます.
便利.

現在, TSのバージョン指定には

3.8-Beta
3.7.5
3.6.3
3.5.1
3.3.3
3.1.6
3.0.1
2.8.1
2.7.2
2.4.1
Nightly

が, targetのバージョン指定には,

ES3
ES5
ES2015
ES2016
ES2017
ESNext

がそれぞれ選択できます.

さて.
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax
を見ると, スプレッド構文はECMAScript 2015(ES6)で追加されたようです.

このあたり, バージョンを切り替えて実際に確認してみます.

ソースのTSは以前CodePenで利用したものとおなじ以下のものを利用します.

ソースのts
[...Array(3)].map((_, index) => console.log(`spread operator ${index}`));

v3.7.5 + ES2017

TypeScript PlayGroundアクセス時のデフォルト値です.
Latest stableのようなのでまぁ問題なく動くでしょう.

トランスパイルされたjs
"use strict";
[...Array(3)].map((_, index) => console.log(`spread operator ${index}`));
コンソール
spread operator 0
spread operator 1
spread operator 2

動きますね.

v3.7.5 + ES3

targetをES3にします.

トランスパイルされたjs
"use strict";
var __spreadArrays = (this && this.__spreadArrays) || function () {
    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
    for (var r = Array(s), k = 0, i = 0; i < il; i++)
        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
            r[k] = a[j];
    return r;
};
__spreadArrays(Array(3)).map(function (_, index) { return console.log("spread operator " + index); });
コンソール
spread operator 0
spread operator 1
spread operator 2

こいつ...動くぞ...! ばんなそかな.
なんだかポリフィルっぽい印象を受けますね.

v2.4.1 + ES2017

トランスパイルされたjs
"use strict";
[...Array(3)].map((_, index) => console.log(`spread operator ${index}`));
コンソール
spread operator 0
spread operator 1
spread operator 2

ほむほむ.

v2.4.1 + ES3

トランスパイルされたjs
"use strict";
Array(3).slice().map(function (_, index) { return console.log("spread operator " + index); });
コンソール

sliceになってうまく動かなくなりましたね.

組み合わせ表

ひととおり触ってみたので一覧にします.
動作した組み合わせがoおよびo!で, 動作しなかった組み合わせがxとなります.
oとo!の違いについては後述します.

TS/target ES3 ES5 ES2015 ES2016 ES2017
3.7.5 o! o! o o o
3.6.3 o! o! o o o
3.5.1 x x o o o
3.3.3 x x o o o
3.1.6 x x o o o
3.0.1 x x o o o
2.8.1 x x o o o
2.7.2 x x o o o
2.4.1 x x o o o

トランスパイルされたJSは3種類に分けられました.

x
"use strict";
Array(3).slice().map(function (_, index) { return console.log("spread operator " + index); });
o
"use strict";
[...Array(3)].map((_, index) => console.log(`spread operator ${index}`));
o!
"use strict";
var __spreadArrays = (this && this.__spreadArrays) || function () {
    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
    for (var r = Array(s), k = 0, i = 0; i < il; i++)
        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
            r[k] = a[j];
    return r;
};
__spreadArrays(Array(3)).map(function (_, index) { return console.log("spread operator " + index); });

動作しないのは一番上のもので, 下の二つは動作しました.
三番目のものを表内ではo!としています.

表を見るとわかりますが, 基本的にES3とES5ではスプレッド構文はうまくトランスパイルされませんが,
TS v3.6.3以降では動作するようになっています. 興味深いですね.

これはちゃんとTS3.6のリリースノートに記述がありました. 神対応.


https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-6.html#more-accurate-array-spread

More Accurate Array Spread

In pre-ES2015 targets, the most faithful emit for constructs like for/of loops and array spreads can be a bit heavy. For this reason, TypeScript uses a simpler emit by default that only supports array types, and supports iterating on other types using the --downlevelIteration flag. The looser default without --downlevelIteration works fairly well; however, there were some common cases where the transformation of array spreads had observable differences. For example, the following array containing a spread

[...Array(5)]

can be rewritten as the following array literal

[undefined, undefined, undefined, undefined, undefined]

However, TypeScript would instead transform the original code into this code:

Array(5).slice();

which is slightly different. Array(5) produces an array with a length of 5, but with no defined property slots.

TypeScript 3.6 introduces a new __spreadArrays helper to accurately model what happens in ECMAScript 2015 in older targets outside of --downlevelIteration. __spreadArrays is also available in tslib.


つまり, CodePenで扱うTypeScriptのバージョンがv3.6以上になったため, スプレッド構文が正常に利用できるようになった, と推測できます.
もしくはES2015以降でトランスパイルするようになったかですが.

さいごに

1年かけて, やっと幾分かスッキリできる内容になりました..._(⌒(_´-ω-`)_
ふにゃふにゃなままで記事を放置するのはよろしくないですよね...すみませんでした...

なにか改善点や勘違いしている点, 有力な情報などがありましたら, コメントいただけますと幸いです₍₍(ง˘ω˘)ว⁾⁾


以下, 以前の記事本文

Qiita: JavaScriptで指定したN回分ループする

ES2015のspread operatorを使ってループさせたい回数nの配列を作り、map(またはforEach)を使ってループさせる。

[...Array(5)].map(() => console.log('5回実行'))

// counterが必要な場合
[...Array(5)].map((_, i) => console.log(i))

CodePenで, 設定からJavaScript Preprocessorを TypeScript にすると, spread operatorでのN回ループは動作しなかった.
一方, None (native JavaScript), Babel では動作した.
https://codepen.io/akinosora/pen/ywmKZM?editors=1111

TypeScript Playgroundでも試してみたが, こちらでもspread operatorでのN回ループは動作しなかった.
https://www.typescriptlang.org/play/

TypeScriptの場合, N回ループしたい場合は Array(N).fill().map(() => doSomething()) のほうが安全かも?

なお, create-react-app--typescript オプションでローカル環境に構築したReact+TypeScript環境では,
spread operatorでのN回ループは動作した. なんでや.

おかげでローカルで動作するコードがCodePen上で動作せず, 暫く頭を悩ませることとなった.


6
2
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2