TypeScript v3.9.2 変更点
TypeScript 3.9がアナウンスされました。
- What's new in TypeScript in 3.9
- Breaking Changes in 3.9
- TypeScript 3.9 Iteration Plan
- TypeScript Roadmap: January - June 2020
Roadmapはこちら。
この辺に僕が試した時のコードを投げてあります。
変更点まとめ
-
Promise.all
関係の型推論を改善 Improvements in Inference and Promise.all- 3.7で
Promise.all
周りの改善を入れたけどリグレッションが発生してた -
undefined
などが絡んだときの型推論がおかしくなってたのを修正
- 3.7で
-
awaited
という演算子の導入をしませんでした What About theawaited
Type?- Promiseを解決したときに得られる値についての挙動を
awaited
としてモデル化しようとした - … というのを3.9で出したかったけど、もうちょっと磨いてから出します
- Promiseを解決したときに得られる値についての挙動を
- 速度面の改善 Speed Improvements *1 *2 *3 *4 *5 *6
- コンパイルにかかる速度を改善した 色々と!
- material-ui や styled-components といった複雑な型を持つライブラリで編集やコンパイルにかかる時間がかなり長かった
- union types, intersections, conditional types, mappted types など色々
-
@ts-expect-error
の追加// @ts-expect-error
Comments- コンパイルエラーを無視させる
- 主にテスト時に不正な使い方をしたいときなどに使える
-
@ts-ignore
は次の行にエラーがなくても怒らないが、@ts-expect-error
はunusedだと怒られる
- 三項演算子で関数を呼ばずに値として評価している箇所をチェックするようにした Uncalled Function Checks in Conditional Expressions
- quick fixもあるよ!
- VSCodeが複数のバージョンのTypeScriptの利用をサポート selecting different versions of TypeScript
- Visual Studio関連の 何 か
- Sublime Text 3が異なるバージョンのTypeScriptの選択をサポートしただとか
- JSでCommonJSスタイルのimportの自動補完をサポート CommonJS Auto-Imports in JavaScript
- コードアクションでなにかを操作したときにより元の改行が尊重されるようになった Code Actions Preserve Newlines
- 関数でreturnを書き忘れていそうなときのQuick Fixを追加 Quick Fixes for Missing Return Expressions
-
return
足してくれたり{}
を削除してくれたり
-
- ソリューションスタイルのtsconfig.jsonのサポート Support for “Solution Style”
tsconfig.json
Files- ソリューションスタイル = そのtsconfig.jsonの対象になるファイルがゼロ (≒ references を持つ)
- 今までは自分が含まれるプロジェクトのtsconfig.json以外で参照されている型も参照可能だったけど(正しく)見えなくなった
- マルチプロジェクトでのシンボルナビゲーションのサポート Editor Support for Multi-Project Symbol Navigation
- Quick Fixで private でプロパティを生成できるようになった Declare a
private
property
破壊的変更!
今回はかなり色々ありますね。とはいえ実プロジェクトで問題になるような変更は少ないでしょう。
-
?.
と!
と組み合わせたときの挙動を修正 Parsing Differences in Optional Chaining and Non-Null Assertions-
foo?.bar!.baz
が(foo?.bar).baz
と評価されていたが、これだとchainが崩れるので修正した
-
- JSXのテキストとして
}
や>
ベタが許されなくなった}
and>
are Now Invalid JSX Text Characters (Quick Fix)-
}
とか>
はJSX的にはテキストの位置にでてきてはいけない - 今までこれをエラーにしていなかったが、エラーにするようにした
-
>
であれば{">"}
か>
を、}
であれば{"}"}
か}
を使うこと
-
- IntersectionとOptionalが絡むときのチェックを改善 Stricter Checks on Intersections and Optional Properties
- Intersectionsでチェックを強化 Intersections Reduced By Discriminant Properties
- 無理なものは無理
- getter/setterが enumerable: true だったのをECMAScriptの仕様にそってfalseでdownpileするように変更 Getters/Setters are No Longer Enumerable
- IssueにSkateJSで困るんだわ… とか2016年に僕がコメントしてて時代を感じる…
-
T extends any
とかしてたやつでTがany相当の振る舞いをしないようにした Type Parameters That Extendany
No Longer Act asany
-
export *
してたらとりあえずJSに出力されるようになったexport *
is Always Retained -
export *
とかを使ったときの live binding で getter を使うよう修正 Exports Now Use Getters for Live Bindings- tslib に
__createBinding
が追加されたり
- tslib に
- exportするときにプロパティを先に初期化するようにした Exports are Hoisted and Initially Assigned
- 1個上の変更に対する互換性維持のため
tsc --init
で生成した tsconfig.json に https://aka.ms/tsconfig.json の案内が出るようになりましたね。
多分今回から…?
次のVersionは4.0らしいです。
このまとめを続けるかどうか悩ましい…
ところで僕のGitHub Sponsorはこちらからどうぞ。
Promise.all
関係の型推論を改善
Promise.all 周りの改善です。
標準の型定義を改善したけどリグレッションで正確じゃない場合があったのを直したらしいです。
let str = "a";
let num : number | undefined = 1 as any;
let strP = Promise.resolve(str);
let numP = Promise.resolve(num);
// rStr1 の型
// TypeScript 3.8 では string | undefined
// TypeScript 3.9 から string
// undefinedのコンタミがなくなった!
let [rStr1, rNum1] = await Promise.all([strP, numP]);
let bool: boolean | null = true as any;
let boolP = Promise.resolve(bool);
// rStr2 の型
// TypeScript 3.8 では string | null | undefined
// TypeScript 3.9 から string
// rNum2 の型
// TypeScript 3.8 では number | null | undefined
// TypeScript 3.9 から number | undefined
// undefined, null のコンタミがなくなって直感的な型に
let [rStr2, rNum2, rBool2] = await Promise.all([strP, numP, boolP]);
なるほどねー…。
awaited
という演算子の導入をしませんでした
しなかったらしいです。
色々問題が発生したのでもうちょっと揉むわー的な。
https://github.com/microsoft/TypeScript/pull/35998
https://github.com/microsoft/TypeScript/issues/37897
https://github.com/microsoft/TypeScript/pull/37610
速度面の改善
なんか色々やったらしいです。
https://github.com/microsoft/TypeScript/pull/36576
https://github.com/microsoft/TypeScript/pull/36590
https://github.com/microsoft/TypeScript/pull/36607
https://github.com/microsoft/TypeScript/pull/36622
https://github.com/microsoft/TypeScript/pull/36754
https://github.com/microsoft/TypeScript/pull/36696
@ts-expect-error
の追加
@ts-expect-error
が追加されました。
すでにある @ts-ignore
に類似のものです。
@ts-ignore
がコンパイルエラーを無視させるのに対して、 @ts-expect-error
もコンパイルエラーを無視させますが、エラーがない場合エラーになります。
function doStuff(a: string, b: string) {
// @ts-expect-error
// コンパイルエラーを抑制することができる (今回追加された)
hello(1);
// @ts-ignore
// コンパイルエラーを抑制することができる (前からある)
hello(1);
// // @ts-expect-error
// error TS2578: Unused '@ts-expect-error' directive.
// 何もしなくてもコンパイルが正しく通る場合、コンパイルエラーになる!
hello("cat");
// @ts-ignore
// 特に効果がなくても特になんもならない
hello("cat");
}
function hello(word: string) {
console.log(`Hello, ${word}`);
}
@ts-ignore
から @ts-expect-error
に乗り換えるべきか?という話題について。
-
@ts-expect-error
がおすすめな状況- テストコードを記述していて、わざとエラーになるような操作を記述したい場合
- 近いうちに修正される予定で、急場をしのぐ回避策がほしい場合
- 働きアリがもりもり働いていて、これらの回避策をすぐ修正してくれそうな場合
-
@ts-ignore
を使ったほうがよい状況- 大きなプロジェクトで、コードのオーナーが不明瞭な箇所がエラーになった場合
- TypeScriptのバージョンを更新しようとして、どちらかのバージョンではエラーになるがもう片方ではエラーにならない場合
- どっちがマシか判断する時間もないような場合
だそうです。
三項演算子で関数を呼ばずに値として評価している箇所をチェックするようにした
タイトルのまんまです。
function hasImportantPermissions(): boolean {
return true;
}
function deleteAllTheImportantFiles() {
}
// 関数の呼び出しを忘れて条件式に使った場合、エラーになる (TypeScript v3.7 で入った)
// error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
if (hasImportantPermissions) {
deleteAllTheImportantFiles();
}
// 三項演算子でもこのチェックが働くようになった (今回から)
// error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
hasImportantPermissions ? deleteAllTheImportantFiles() : void 0;
// なお、 Add missing call parentheses のQuick Fixも追加されたので、エラーになっている箇所を見つけたらシュッと直せる
VSCodeが複数のバージョンのTypeScriptの利用をサポート
前からできなかったっけ…?
Visual Studio関連の何か
興味がないので調査してない
Sublime Text 3が異なるバージョンのTypeScriptの選択をサポート
興味がないので調査してない
JSでCommonJSスタイルのimportの自動補完をサポート
らしいです。
const { } = require("fs");
// ここで readFileSync とかが補完候補にあがり、選ぶとimport文にいい感じに追加される
コードアクションでなにかを操作したときにより元の改行が尊重されるようになった
地味に不便してたやつです。
const maxValue = 100;
// start から end の範囲を Extract to function in Global scope のrefactorを行うとする
/*start*/
for (let i = 0; i <= maxValue; i++) {
// First get the squared value.
let square = i ** 2;
// Now print the squared value.
console.log(square);
}
/*end*/
// 今まで 改行のみ の行は尊重されず、潰されてしまっていた
function newFunction1() {
for (let i = 0; i <= maxValue; i++) {
// First get the squared value.
let square = i ** 2;
// Now print the squared value.
console.log(square);
}
}
// 3.9 からちゃんと元の改行が保持されるようになった
function newFunction2() {
for (let i = 0; i <= maxValue; i++) {
// First get the squared value.
let square = i ** 2;
// Now print the squared value.
console.log(square);
}
}
関数でreturnを書き忘れていそうなときのQuick Fixを追加
まんまです。
let numberArray = [1, 9, 2, 8, 3, 7, 4, 6, 5];
// sort(compareFn?: (a: T, b: T) => number): this; という定義
// 次の書き方だと sort に渡した関数が値を返していない(のでコンパイルエラーになる)
// numberArray.sort((a, b) => { a - b });
// Quick Fix が追加されているので試してみよう!
// Add a return statement を選ぶと次のように変形される
numberArray.sort((a, b) => { return a - b; });
// Remove block body braces を選ぶと次のように変形される
numberArray.sort((a, b) => a - b);
ソリューションスタイルのtsconfig.jsonのサポート
ソリューションスタイル = そのtsconfig.jsonの対象になるファイルがゼロ (≒ references を持つ) 状態だそうです。
Visual Studioのslnとprj的な感じなんですかね。
今までは自分が含まれるプロジェクトのtsconfig.json以外で参照されている型も参照可能だったけど(正しく)見えなくなったらしいです。
https://github.com/microsoft/TypeScript/issues/27372 で報告されているやつ。
報告社は再現用のリポジトリも用意している。
https://github.com/OliverJAsh/jest-ts-project-references
次のような tsconfig.json を使っているとする。
{
// By default, all files will be included. We want to use our project references instead, so we
// clear these.
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig-src.json"
},
{
"path": "./tsconfig-tests.json"
}
]
}
srcのほうでは "types": []
、testsのほうでは "types": ["jest"]
だったとしても、src側でjestの型定義が参照してコンパイルとかも通っていた。
のを、しっかり管理するようにしてそれぞれのファイルが自分が影響される設定に従ってチェックされるようになった… という感じのようだ。
マルチプロジェクトでのシンボルナビゲーションのサポート
なんかできるようになったらしいです。
Quick Fixで private でプロパティを生成できるようになった
そのまんま。
class A {
constructor() {
// Declare private property '_x' が追加
this._x = 10;
}
}
?.
と !
と組み合わせたときの挙動を修正
JSにdownpileされるときに生成されるコードが修正されたという話ですね。
declare var foo: undefined | {
bar: undefined | {
baz: string;
};
};
// v3.8 までは v1 の型は string になっていた (実行時エラーになる可能性がある)
// v3.9 からは v1 の型は string | undefined になるようになった
let v1 = foo?.bar!.baz;
// --target es2019 で出力した場合
// v3.8 までの結果は次のとおり (undefined).baz という実行パスを通る可能性がある
// let v1 = (foo === null || foo === void 0 ? void 0 : foo.bar).baz;
// v3.9 からの結果は次のとおり optional chaining がぶった切られる問題がなくなった
// let v1 = foo === null || foo === void 0 ? void 0 : foo.bar.baz;
// v3.9 で今までと同じ結果にするにはこう書く必要がある 同じ結果にしたいことはないと思うけど…
let v2 = (foo?.bar)!.baz;
JSXのテキストとして }
や >
ベタが許されなくなった
許されてたのはinvalidだったらしいです。
そうだったんか…!
// JSXのテキストで、JSXの仕様的に使ってはいけない > とか } の利用がコンパイルエラーになるようになった
// error TS1382: Unexpected token. Did you mean `{'>'}` or `>`?
// error TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
let node = (
<div>
テキストに > とか } があったらダメ!
これからは {">"} とか {"}"} を使うか、
> や } を使おう!
</div>
);
// Quick Fix も2パターン追加されている
// Wrap invalid character in an expression container
// Convert invalid character to its html entity code
// …が、 v3.9.2 + VSCode 1.46.0-insider の組み合わせだと選んでも直してくれないぽい
IntersectionとOptionalが絡むときのチェックを改善
まだこの手のが発掘されるのってすげーな… と思ってしまう…。
interface A {
a: number; // ここの a は number
}
interface B {
b: string;
}
interface C {
a?: boolean; // ここの a は boolean
b: string;
}
declare let x: A & B;
declare let y: C;
// y = x;
// ↑ 上記コードは A と C は一致しないが B と C は一致するので許されていた
// が、当然実行時エラーになる可能性がある
// v3.9 から、 A & B は C に一致しないので怒られるようになった
// error TS2322: Type 'A & B' is not assignable to type 'C'.
// Types of property 'a' are incompatible.
// Type 'number' is not assignable to type 'boolean | undefined'.
getter/setterが enumerable: true だったのをECMAScriptの仕様にそってfalseでdownpileするように変更
classでgetter, setterがある場合、それがは enumerable: false
で生成されるのがECMAScriptの仕様的には正しいが true
で生成されたのを修正。
Issueを見ると、2016年に僕がSkateJSでbabelと互換性無くて困る〜〜みたいなことを言ってて、時代を感じますね。
その時はあまり簡単に直せるようなコードベースだった気がするのでPR送らなかったんだと思いますが、わりとシュッと治っていてびびる。
class A {
get a() {
return "a";
}
set a(v: string) {
console.log(v);
}
}
// v3.8 での出力
// var A = /** @class */ (function () {
// function A() {
// }
// Object.defineProperty(A.prototype, "a", {
// get: function () {
// return "a";
// },
// set: function (v) {
// console.log(v);
// },
// enumerable: true,
// configurable: true
// });
// return A;
// }());
// v3.9 での出力
// var A = /** @class */ (function () {
// function A() {
// }
// Object.defineProperty(A.prototype, "a", {
// get: function () {
// return "a";
// },
// set: function (v) {
// console.log(v);
// },
// enumerable: false,
// configurable: true
// });
// return A;
// }());
T extends any
とかしてたやつでTがany相当の振る舞いをしないようにした
そのまんまです。
function foo<T extends any>(arg: T) {
// v3.8 までは T は any として扱われてたのでエラーなし
// v3.9 からは T は extends unknown と同等に振る舞いっぽ
// error TS2339: Property 'notExists' does not exist on type 'T'.
arg.notExists;
}
これはまぁなんというか元のままでもよかったのでは…という気もする。
lintが extends any
とかするなっ!って言ってくれればよいようなまぁ改善のような…。
export *
してたらとりあえずJSに出力されるようになった
何もexportしてないような .ts があるとして
export {}
これを適当に export *
してた場合、生成するJSからはomitされてたんだけどこれが何も考えずにベタに出るようになった。
export * from "./empty";
// v3.8 では参照先が何もexportしていない場合、JSの出力ではexportは削られていた
// v3.9 では何も考えずとりあえず出力するようになった
マジカルが減る方向性なのでよいと思います。
v3.8で import type
も導入されているので、適宜必要なところで使っていきたいですね。
export *
とかを使ったときの live binding で getter を使うよう修正
そもそもECMAScriptがそういう仕様なの知らなかった…。
変数をexportし、その変数を変えられるような構造を持つとする。
export let data = 1;
export function setData(v: number) {
data = v;
}
これを参照し使った場合、定義していたモジュールを直接参照している場合はそれっぽく動いている。
import { data, setData } from "./a";
console.log("b-1", data);
setData(2);
console.log("b-2", data);
// v3.8, v3.9 ともに --module commonjs の場合 次のように出力される
// b-1 1
// b-2 2
export { data, setData };
が、間接的に参照された場合、変数がgetter経由で提供されていないので、関係性が断絶してしまう。
のが、今回治った、というお話。
import { data, setData } from "./b";
console.log("c-1", data);
setData(3);
console.log("c-2", data);
// v3.8 だと --module commonjs の場合 次のように出力される
// c-1 1
// c-2 1
// v3.9 だと --module commonjs の場合 次のように出力される
// c-1 2
// c-2 3
export { data, setData };
この挙動をサポートするため、 tslib に __createBinding
みたいなヘルパ関数が追加されました。
tslib も v2.0.0 になり、 v1 系は TypeScript 3.8 まで用、 v2 で TypeScript 3.9 以降用と使い分ける必要があるようです。
実際に __createBinding
が使われる出力を得たい場合、次のようなコードを試す。
import * as b from "./b";
export * from "./b";
// __createBinding みたいなヘルパ関数が増えた
// tslib も v2.0.0 になった様子 v1 系は TypeScript 3.8 まで用、 v2 で TypeScript 3.9 以降用
b.data;
exportするときにプロパティを先に初期化するようにした
1つ上の変更による余波。
export let nameFromFoo = true;
export * from "./foo";
// ./foo でも nameFromFoo を 定義して export している
// なので、ここで上書きできなければならない
export const nameFromFoo = 0;
// 出力されるJSコードの比較
// v3.8 までの出力 特に気にする点はない
// __export(require("./foo"));
// exports.nameFromFoo = 0;
// v3.9 からの出力 先に undefined で初期化されるようになった
// exports.nameFromFoo = void 0;
// __exportStar(require("./foo"), exports);
// exports.nameFromFoo = 0;
// __exportStar の中で __createBinding を使うようになった
// その中で getter を定義してしまうので、後から上書きできない
// TypeError: Cannot set property nameFromFoo of #<Object> which has only a getter
// 上書きしたい場合、先にプロパティを作っておくと無視してくれるので別途後から値を設定する
__exportStar
の中で __createBinding
を使うようになった。
それで、その中で getter を定義してしまうので、後から上書きできない。
先にプロパティの存在を主張しておかないと、 TypeError: Cannot set property nameFromFoo of #<Object> which has only a getter
とか言われてしまうため。
上書きしたい場合、先にプロパティを作っておくと無視してくれるので別途後から値を設定するようなコードが生成されるようになった。
import { nameFromFoo } from "./bar";
// 0 と表示される
console.log(nameFromFoo);