💊

TypeScript v3.x 主要な変更のおさらい

2021/02/27に公開1

先日、TS v4.2がリリースされたので、この機会にv3のマイナーバージョンごとの主な変更をおさらいしようと思います。
次回、v4の変更点・追加点を見ていきたいと思います。

v3.xごとの主な機能的な変更

v3.0

  • tuple型の強化(パラメータリストとの統合)
// Both `y` and `z` are optional here.
function foo(x: boolean, y = 100, z?: string) {
    // ...
}

foo(true);
foo(true, undefined, "hello");
foo(true, 200);

// `rest` accepts any number of strings - even none!
function foo(...rest: string[]) {
    // ...
}

foo();
foo("hello");
foo("hello", "world");
  • JSXでのdefaultPropsのサポート
function Greet({ name = "world" }: Props) {
    return <div>Hello ${name.toUpperCase()}!</div>;
}
  • プロジェクト間の参照サポート
  • unknown型の追加

v3.1

  • tupple型とarray型を処理する際のmapped typeの改善
type Stringify<T> = {
    [K in keyof T]: string
};

declare function stringifyAll<T extends unknown[]>(...elements: T): Stringify<T>;

let stringyCoordinates = stringifyAll(100, true);

// No errors!
let first: string = stringyCoordinates[0];
let second: string = stringyCoordinates[1];

let len: 2 = stringyCoordinates.length
// Type 'string' is not assignable to type '2'.

stringyCoordinates.forEach(x => console.log(x));
// Cannot invoke an expression whose type lacks a call signature. Type 'String' has no compatible call signatures.

従来まで、このような場合、stringyCoordinatesが持つ、lengthやforEachなどのプロパティがstringと判定されていたのが改善された。

  • 関数にプロパティを簡単に生やすことができるように
export const FooComponent => ({ name }) => (
    <div>Hello! I am {name}</div>
);

FooComponent.defaultProps = {
    name: "(anonymous)",
};

v3.2

  • ジェネリクスでのオブジェクトスプレッド
let person = { name: "Daniel", location: "New York City" };

// My secret revealed, I have two clones!
let shallowCopyOfPerson = { ...person };
let shallowCopyOfPersonWithDifferentLocation = { ...person, location: "Seattle" };
TypeScript does a pretty good job here when it has enough information about the type. The type system closely tries to model the behavior of spreads and overwrites new properties, tries to ignore methods, etc. But unfortunately up until now it wouldn't work with generics at all.

function merge<T, U>(x: T, y: U) {
    // Previously an error!
    return { ...x, ...y };
}

// Returns '{ name: string, age: number, greeting: string } & T'
function foo<T>(obj: T) {
    let person = {
        name: "Daniel",
        age: 26
    };

    return { ...person, greeting: "hello", ...obj };
}

従来まではmergeのような関数はエラーになっていたが、改善された。

  • bind、callとapplyで型推論が改善
function foo(a: number, b: string): string {
    return a + b;
}

let a = foo.apply(undefined, [10]);              // error: too few argumnts
let b = foo.apply(undefined, [10, 20]);          // error: 2nd argument is a number
let c = foo.apply(undefined, [10, "hello", 30]); // error: too many arguments
let d = foo.apply(undefined, [10, "hello"]);     // okay! returns a string

bind、callとapplyの型推論が改善され、従来までエラーとして検出できなかったものが検出できるように。

v3.3

  • Union型の改善
interface A {
    aProp: string;
    commonProp: string;
}

interface B {
    bProp: number;
    commonProp: number
}

type Union = A | B;

declare let x: Union;

x.aProp; // error - 'B' doesn't have the property 'aProp'
x.bProp; // error - 'A' doesn't have the property 'bProp'
x.commonProp; // okay! Both 'A' and 'B' have a property named `commonProp`.

Union型で共通しているプロパティにアクセスできる

type Fruit = "apple" | "orange";
type Color = "red" | "orange";

type FruitEater = (fruit: Fruit) => number;     // eats and ranks the fruit
type ColorConsumer = (color: Color) => string;  // consumes and describes the colors

declare let f: FruitEater | ColorConsumer;

f("orange"); // It works! Returns a 'number | string'.

f("apple");  // error - Argument of type '"apple"' is not assignable to parameter of type '"orange"'.

f("red");    // error - Argument of type '"red"' is not assignable to parameter of type '"orange"'.

関数も呼べるが、共通している型しか渡せない。

  • 大規模なプロジェクトへの対応

複合プロジェクトという新機能が導入され、大規模なプロジェクトを小さな単位に分割することで、TSの恩恵を損なうことなく、迅速にbuildすることができる。

v3.4

  • incrementalフラグ

TSでの開発において、ソースに変更が加えられた際に、ビルド時間が短いことは重要である。
これまではwatchフラグを使用することで、変更により影響を受ける箇所のみを再ビルドしていた。
incrementalフラグは前回のコンパイルからの情報を保存し、その情報を使用することで型チェックを行うための最もコストの少ない方法を特定します。

  • readOnlyの配列とタプル型
function foo(arr: ReadonlyArray<string>) {
    arr.slice();        // okay
    arr.push("hello!"); // error!
}

function foo(pair: readonly [string, string]) {
    console.log(pair[0]);   // okay
    pair[1] = "hello!";     // error
}

v3.5

  • omitの導入
type Person = {
    name: string;
    age: number;
    location: string;
};

type QuantumPerson = Pick<Person, "location">;

// equivalent to
type QuantumPerson = {
    name: string;
    age: number;
};

特定のプロパティを除いた型を作成します。

  • Union型での過剰なプロパティに対する型チェックの改善
type Point = {
    x: number;
    y: number;
};

type Label = {
    name: string;
};

const thing: Point | Label = {
    x: 0,
    y: 0,
    name: true
};

従来は、nameがboolにも関わらず、エラーが発生しませんでしたが、エラーになるようになった。

v3.6

  • イテレータとジェネレータに対して、厳密なチェックができるようになった
function* foo() {
    if (Math.random() < 0.5) yield 100;
    return "Finished!"
}

let iter = foo();
let curr = iter.next();
if (curr.done) {
    // TypeScript 3.5 and prior thought this was a 'string | number'.
    // It should know it's 'string' since 'done' was 'true'!
    curr.value
}

従来は、ジェネレータを使った場合にyieldされた値かreturnされた値か判別できなかったができるようになった。

function* bar() {
    let x: { hello(): void } = yield;
    x.hello();
}

let iter = bar();
iter.next();
iter.next(123); // oops! runtime error!

また、ジェネレータはyieldの型をanyとしか推論できませんでしたが、修正されました。

v3.7

  • optional chaining
let x = foo?.bar.baz();

nullまたはundefinedの場合に処理を停止するように書けるようになった。
fooがnullまたはundefinedの場合、xはundefinedになる。

  • Nullish Coalescing
let x = foo ?? bar();

nullまたはundefinedの場合に、デフォルト値へのフォールバックをさせることができる。
fooがnullまたはundefinedの場合、xはbar()の返り値になる。

let y = hugu || 0.5

||を使用すると、huguが0の場合でもyは0.5となる。
このようなバグの温床を作らないように??が作られた。

  • Assertion Functions
function multiply(x, y) {
    assert(typeof x === "number");
    assert(typeof y === "number");

    return x * y;
}

assertの引数に渡された条件がfalseの場合、
AssertionErrorを投げ、処理が落ちる。

v3.8

  • クラスのプライベートフィールドのサポート
class Person {
    #name: string

    constructor(name: string) {
        this.#name = name;
    }

    greet() {
        console.log(`Hello, my name is ${this.#name}!`);
    }
}

let jeremy = new Person("Jeremy Bearimy");

jeremy.#name
//     ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.

プライベートフィールドがサポートされ、クラス外で使用されるとエラーになる。

  • Top-Level await
const response = await fetch("...");
const greeting = await response.text();
console.log(greeting);

// Make sure we're a module
export {};

asyncがつけられた関数内でしかawaitは使えなかったが、
モジュールの最上位で使用できるようになった。

v3.9

  • Promise.allでの型推論の改善
interface Lion {
    roar(): void
}

interface Seal {
    singKissFromARose(): void
}

async function visitZoo(lionExhibit: Promise<Lion>, sealExhibit: Promise<Seal | undefined>) {
    let [lion, seal] = await Promise.all([lionExhibit, sealExhibit]);
    lion.roar(); // uh oh
//  ~~~~
// Object is possibly 'undefined'.
}

特に、nullやundefinedを一緒に使った際に、型推論の結果が正しくなくなることがあったが、修正された。

Discussion

turusuketurusuke

記事に誤記があるようでしたので、コメントで連絡させていただきます。

v3.1

  • 関数にプロパティを簡単に生やすことができるように
- export const FooComponent => ({ name }) => (
+ export const FooComponent = ({ name }) => (
    <div>Hello! I am {name}</div>
);

v3.5

  • omitの導入
type Person = {
    name: string;
    age: number;
    location: string;
};

- type QuantumPerson = Pick<Person, "location">;
+ type QuantumPerson = Omit<Person, "location">;

// equivalent to
type QuantumPerson = {
    name: string;
    age: number;
};