LoginSignup
110
60

More than 3 years have passed since last update.

TypeScript v3.9.2 変更点

Last updated at Posted at 2020-05-17

TypeScript v3.9.2 変更点

こんにちはメルペイ社@vvakameです。

TypeScript 3.9がアナウンスされました。

Roadmapはこちら

この辺に僕が試した時のコードを投げてあります。

変更点まとめ

  • Promise.all 関係の型推論を改善 Improvements in Inference and Promise.all
    • 3.7で Promise.all 周りの改善を入れたけどリグレッションが発生してた
    • undefined などが絡んだときの型推論がおかしくなってたのを修正
  • awaited という演算子の導入をしませんでした What About the awaited Type?
    • Promiseを解決したときに得られる値についての挙動を awaited としてモデル化しようとした
    • … というのを3.9で出したかったけど、もうちょっと磨いてから出します
  • 速度面の改善 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

破壊的変更!

今回はかなり色々ありますね。とはいえ実プロジェクトで問題になるような変更は少ないでしょう。

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 `&gt;`?
// error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
let node = (
    <div>
        テキストに > とか } があったらダメ!
        これからは {">"} とか {"}"} を使うか、
        &gt;&rbrace; を使おう!
    </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 があるとして

empty.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し、その変数を変えられるような構造を持つとする。

a.ts
export let data = 1;

export function setData(v: number) {
    data = v;
}

これを参照し使った場合、定義していたモジュールを直接参照している場合はそれっぽく動いている。

b.ts
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経由で提供されていないので、関係性が断絶してしまう。
のが、今回治った、というお話。

c.ts
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 が使われる出力を得たい場合、次のようなコードを試す。

d.ts
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つ上の変更による余波。

foo.ts
export let nameFromFoo = true;
bar.ts
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 とか言われてしまうため。
上書きしたい場合、先にプロパティを作っておくと無視してくれるので別途後から値を設定するようなコードが生成されるようになった。

use.ts
import { nameFromFoo } from "./bar";

// 0 と表示される
console.log(nameFromFoo);
110
60
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
110
60