TypeScript 3.7 で止まった知識をアップデートしたメモ(4.2まで)

2021/05/15に公開

こんにちは、 nerikosans です。
TypeScript 4.3 RCが 発表された そうですね。
これはそろそろ知識をアップデートしないとなと思い、長らく3.7~8あたりで止まっていた知識をアップデートすべく、公式のWhat's newを読みながら、気になった機能についてメモを書いていこうと思います。

TypeScript 3.8

Type-only import/export

  • import type により、typeのみをimportできるようになった。
  • TSの文脈でのみ有効な指定であり、トランスパイル時にはこの記述は完全に削除される。
import type { HogeType } from './myModule';

Private fields

  • classのfieldを # で始めることで、完全にprivateなfieldを宣言できるようになった。
class Person {
  #name: string;

  constructor(name: string) {
    this.#name = name;
  }
}
  • classに完全に固有のfieldとなり、継承先で同じ名前のfieldを宣言しても別のものと認識される。
  • private 修飾子による宣言と違って、js runtimeからもアクセスはできない。

そのほか

  • *as 付きで直接exportできるようになった。
export * as myName from 'someModule';

TypeScript 3.9

3.9ではSyntaxに大きな変更はなく、挙動の修正などが多かったようです。

Type Intersectionの挙動変更

  • C 型の変数に A & B 型の変数を代入するときのチェックが改善された。
  • 従来は AB のどちらかがCに代入可能なら、 A & B 型も代入可能だった。
    • 下の例では、 BC に代入可能なので、エラーなし
  • 修正後は、A & B 型全体が C に代入可能であることが求められる。
    • 下の例では、プロパティ a があっていないので、エラー
interface A {
    a: number; // notice this is 'number'
}

interface B {
    b: string;
}

interface C {
    a?: boolean; // notice this is 'boolean'
    b: string;
}

declare let x: A & B;
declare let y: C;

y = x;

公式サンプルより

Type 'A & B' is not assignable to type 'C'.
  Types of property 'a' are incompatible.
    Type 'number' is not assignable to type 'boolean | undefined'.

このエラーが出るようになった

Typescript 4.0

Variadic Tuple Types

  • Tuple Type内でgeneric typeをspread (...T) できるようになった。
// 配列から最初の要素を抜いて返す関数
function tail<T extends any[]>(arr: readonly [any, ...T]) {
  const [_ignored, ...rest] = arr;
  return rest;
}
  • Tuple Type内で、末尾以外でもspreadが使用できるようになった。
type Strings = [string, string];
type Numbers = number[];
type Unbounded = [...Strings, ...Numbers, boolean];
  • 合わせ技1: 2つの配列を結合する関数
type Arr = readonly any[];

function concat<T extends Arr, U extends Arr>(arr1: T, arr2: U): [...T, ...U] {
  return [...arr1, ...arr2];
}
  • 合わせ技2: 関数の一部の引数だけを先に渡す関数
type Arr = readonly unknown[];

function partialCall<T extends Arr, U extends Arr, R>(
  f: (...args: [...T, ...U]) => R,
  ...headArgs: T
) {
  return (...tailArgs: U) => f(...headArgs, ...tailArgs);
}

Labeled Tuple Elements

  • Tuple Typeにlabelをつけられるようになった。
type Range = [number, number]; // これを
type LabeledRange = [start: number, end: number]; // こう
  • Overloadを書くときとかに便利
type Name = 
  | [first: string, last: string] 
  | [first: string, middle: string, last: string];
  
function createPerson(...name: Name) {}

const p1 = createPerson("Taro", "Yamada");
const p2 = createPerson("Taro", "Harris", "Yamada");

New assignment operators

  • &&=, ||=, ??= が使えるようになった。
  • このoperatorを使うと、必要なときだけ右辺の評価と代入がされるようになって、速くなるかも。
const obj = {
    get prop() {
        console.log("getter has run");
        return Math.random() < 0.5;
    },
    set prop(_val: boolean) {
        console.log("setter has run");
    }
};

function foo() {
    console.log("right side evaluated");
    return true;
}

// 常にfoo()を評価してsetterが走る
obj.prop = obj.prop || foo();

// 必要なときだけfoo()を評価してsetterが走る
obj.prop ||= foo();

そのほか

  • try {} catch (e) {} したときに e: unknown を指定できるようになった。
  • jsxFragmentFactory オプションでFragmentのコンパイル方法を指定できるようになった。

Typescript 4.1

TypeScript: Documentation - TypeScript 4.1

Template Literal Types

  • 文字列typeを宣言するとき、Template Literalを使えるようになった。
type World = "world";

type Greeting = `hello ${World}`;
// type Greeting = "hello world"
type VerticalAlignment = "top" | "middle" | "bottom";
type HorizontalAlignment = "left" | "center" | "right";

// Takes
//   | "top-left"    | "top-center"    | "top-right"
//   | "middle-left" | "middle-center" | "middle-right"
//   | "bottom-left" | "bottom-center" | "bottom-right"

declare function setAlignment(value: `${VerticalAlignment}-${HorizontalAlignment}`): void;
  • Uppercase<T>, Lowercase<T>, Capitalize<T>, Uncapitalize<T> が使えるようになった。
type HELLO = `${Uppercase<"hello">}` // => "HELLO"

Key Remapping in Mapped Types

  • Mapped Typeを使うとき、 as によってkey typeを変換して扱えるようになった。
type MappedTypeWithNewKeys<T> = {
    [K in keyof T as NewKeyType]: T[K]
}
type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};

interface Person {
    name: string;
    age: number;
    location: string;
}

type LazyPerson = Getters<Person>;
/*
type LazyPerson = {
    getName: () => string;
    getAge: () => number;
    getLocation: () => string;
}
*/

Typescript 4.2

Leading/Middle Rest Elements in Tuple Types

  • Tuple Type内で、末尾以外でもrest element( ...string[] とか) を使えるようになった。
    • ただし、1つのTuple内で1箇所だけ。
    • かつ、optional elementとは同居できない。
type A = [...string[], number]; // OK

type B = [boolean, ...string[], boolean]; // OK

type C = [...string[], ...number[]];
// Error: A rest element cannot follow another rest element.

type D = [...string[], number?];
// Error: An optional element cannot follow a rest element.

Variadic Tuple Types(TS4.0)との関係は?

  • そもそも4.0の時点で以下のように書けた。
    • 外部で定義した型をspreadするぶんにはどこでもよい、ということらしい
    • というより、restが末尾になるように無理やり解釈されていそう

TS 4.0.5

type Strings = string[];
type Numbers = number[];
type Pair = [number, number];
type A = [boolean, ...Strings, boolean]; // OK

type B = [boolean, ...string[], boolean]; // NG
// Error: A rest element must be last in a tuple type.

type C = [boolean, ...Strings, ...Numbers, boolean]; // OK
// type C = [boolean, ...(string | number | boolean)[]]

type D = [boolean, ...Strings, ...Pair, boolean]; // OK
// type D = [boolean, ...(string | number | boolean)[]]

Playground

  • 4.2だと以下のようになる。
    • 外部の型を複数spreadしたときの挙動が賢くなっている (type C, D)

TS 4.2.3

type A = [boolean, ...Strings, boolean]; // OK
type B = [boolean, ...string[], boolean]; // OK
type C = [boolean, ...Strings, ...Numbers, boolean]; // OK
// type C = [boolean, ...(string | number)[], boolean]

type D = [boolean, ...Strings, ...Pair, boolean]; // OK
// type D = [boolean, ...string[], number, number, boolean]

type E = [boolean, ...string[], ...number[], boolean]; // NG
// Error: A rest element cannot follow another rest element.

Playground

Destructured Variables Can Be Explicitly Marked as Unused

  • destructionによって変数定義をするとき、 _ を最初につけると、「使わない」変数として扱えるようになった。
    • noUnusedLocals が出なくなる
let [_first, second] = getValues();
// _first を使わなくても良い

おわり

こんなもんでしょうか。
すべての変更は網羅していませんが、いろいろナウいことを勉強できてよかったです。

ここに書かなかった変更について、Index Signatureの周りでいろいろ議論がありそうだったので、また調べて書くかもしれません。

ではまた!

Discussion