1. 前に書く
TypeScript 5.0 は 2023 年 3 月 16 日にリリースされ、多くの新機能とパフォーマンスの最適化が追加されました。新しいバージョンの TypeScript のより重要な変更点を見てみましょう。
2. 新機能
2-1. 速度とパッケージボリュームの最適化
1 つ目は、新しいバージョンのパフォーマンスの向上です。バージョン 5.0 では、ビルド速度とパッケージのボリュームの点で優れた最適化が行われています。次の表は、4.9 と比較したバージョン 5.0 のパフォーマンスの向上を示しています。
計画 | TS 4.9と比較した最適化の程度 |
---|---|
マテリアル UI のビルド時間 | 90% |
TypeScript コンパイラの起動時間 | 89% |
TypeScript コンパイラのセルフビルド時間 | 87% |
Outlook Web のビルド時間 | 82% |
VS Codeのビルド時間 | 80% |
npm パッケージのサイズ | 59% |
TypeScript 5.0 はパフォーマンスを最適化するために具体的に何をしますか?
- 1 つ目は、
namespace
に移行するmodule
ことです。これにより、最新のビルド ツールのより多くの機能が適用され、最適化 (スコープホイスティングなど) され、一部の非推奨コードが削除され、バンドル サイズが約 26.4 MB 削減されます。 - 次に、TS はコンパイラ内のオブジェクトに格納されるデータを簡素化し、メモリ使用量を削減します。
- 次に、解析パフォーマンスを向上させるために、またはクロージャ
var
の代わりにクロージャで時折使用するなど、いくつかの特定の領域で最適化されます。let
const
全体的に、ほとんどの TypeScript プロジェクトでは、TypeScript 5.0 からパフォーマンスが 10% ~ 20% 向上しています。
2-2. 新しいデコレータ規格
デコレータは ts を作成する人にはよく知られており、標準の js 機能ではありませんが、ts は以前のバージョンですでに「実験的な」デコレータをサポートしています。最新バージョン 5.0 では、デコレータ構文は「実験的」構文ではなくなり、コンパイル--experimentalDecorators
オプションに構成項目を追加する必要はなくなりました (ただし、新しいバージョンでもこのコンパイル オプションは引き続き存在します)。TypeScript 5.0 では、デコレータ構文をネイティブにサポートします。
デコレータの例を見てみましょう -
まず、非常に単純なクラスがあります。
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
greet() {
console.log(`Hello, my name is ${
this.name}.`);
}
}
const p = new Person("zy");
p.greet();
このクラスの関数にログを追加し、呼び出し関数の名前を記録したいgreet
ので、最も簡単な方法は次のようにすることです。
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
greet() {
console.log("LOG: Entering method greet.");
console.log(`Hello, my name is ${
this.name}.`);
console.log("LOG: Exiting method greet.");
}
}
const p = new Person("zy");
p.greet();
loggedMethod
しかし、同様の関数をさらに多くの関数に追加したい場合は、デコレータを使用するのが非常に適しています。たとえば、次のように1 つを記述できます。
function loggedMethod(
originalMethod: any,
context: ClassMethodDecoratorContext
) {
const methodName = String(context.name);
function replacementMethod(this: any, ...args: any[]) {
console.log(`LOG: Entering method '${
methodName}'.`);
const result = originalMethod.call(this, ...args);
console.log(`LOG: Exiting method '${
methodName}'.`);
return result;
}
return replacementMethod;
}
このようにして、loggedMethod
このデコレータを使用してこの関数を装飾し、上記の効果を実現できます。
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
@loggedMethod
greet() {
console.log(`Hello, my name is ${
this.name}.`);
}
}
const p = new Person("zy");
p.greet();
// Output:
//
// LOG: Entering method 'greet'.
// Hello, my name is zy.
// LOG: Exiting method 'greet'.
「デコレータ関数を返す関数」を作成することもできるため、デコレータ用にさらにカスタマイズされた関数を開発できます。たとえば、コンソールに出力される文字列のプレフィックスをカスタマイズしたいとします。
function loggedMethod(headMessage = "LOG:") {
return function actualDecorator(
originalMethod: any,
context: ClassMethodDecoratorContext
) {
const methodName = String(context.name);
function replacementMethod(this: any, ...args: any[]) {
console.log(`${
headMessage} Entering method '${
methodName}'.`);
const result = originalMethod.call(this, ...args);
console.log(`${
headMessage} Exiting method '${
methodName}'.`);
return result;
}
return replacementMethod;
};
}
このようにして、loggedMethod
デコレータとして使用される前にこれを呼び出して、コンソール出力文字列のプレフィックスとしてカスタム文字列を渡すことができます。
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
@loggedMethod("LOG:")
greet() {
console.log(`Hello, my name is ${
this.name}.`);
}
}
const p = new Person("zy");
p.greet();
// Output:
//
// LOG: Entering method 'greet'.
// Hello, my name is zy.
// LOG: Exiting method 'greet'.
2-3.const
型パラメータ
オブジェクトの型を推論するとき、ts は通常、ジェネリック型を選択します。たとえば、この場合、names
の型は次のように推論されますstring []
。
type HasNames = {
readonly names: string[] };
function getNamesExactly<T extends HasNames>(arg: T): T["names"] {
return arg.names;
}
// Inferred type: string[]
const names = getNamesExactly({
names: ["Alice", "Bob", "Eve"] });
推論には問題ありませんstring []
が、 yesnames
ですがreadonly
推論された型が ではないためreadonly
、問題が発生します。次のように追加することでこれを修正できますas const
。
// The type we wanted:
// readonly ["Alice", "Bob", "Eve"]
// The type we got:
// string[]
const names1 = getNamesExactly({
names: ["Alice", "Bob", "Eve"] });
// Correctly gets what we wanted:
// readonly ["Alice", "Bob", "Eve"]
const names2 = getNamesExactly({
names: ["Alice", "Bob", "Eve"] } as const);
でも、こうやって書くのは面倒だし、忘れがちです。const
そのため、TypeScript 5.0 では、型パラメータ宣言に修飾子を直接追加して、定数の型推論をデフォルト値に変えることができます。
type HasNames = {
names: readonly string[] };
function getNamesExactly<const T extends HasNames>(arg: T): T["names"] {
// ^^^^^
return arg.names;
}
// Inferred type: readonly ["Alice", "Bob", "Eve"]
// Note: Didn't need to write 'as const' here
const names = getNamesExactly({
names: ["Alice", "Bob", "Eve"] });
具体的な詳細: https://github.com/microsoft/TypeScript/pull/51865
2-4.extends
設定項目は複数の設定ファイルをサポートします
extends
私の意見では、構成アイテムが複数の構成ファイルをサポートすることは、TypeScript 5.0 の最も実用的な機能の 1 つです。複数のプロジェクトを管理するためにを使用する場合tsconfig.json
、多くの場合、構成拡張は次のような「ベースライン」構成ファイルに対して実行されます。
// packages/front-end/src/tsconfig.json
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "../lib",
// ...
}
}
ただし、多くの場合、複数の構成ファイルから拡張する必要がありますが、TypeScript 4.9 以前のバージョンではこの機能がサポートされていません。そして良いニュースは、Typescript 5.0 では extends フィールドで複数の構成パスを導入できるようになったということです。例えば以下のような書き方です。
// tsconfig1.json
{
"compilerOptions": {
"strictNullChecks": true
}
}
// tsconfig2.json
{
"compilerOptions": {
"noImplicitAny": true
}
}
// tsconfig.json
{
"extends": ["./tsconfig1.json", "./tsconfig2.json"],
"files": ["./index.ts"]
}
この例では、strictNullChecks
と の両方がnoImplicitAny
最後の で有効になりますtsconfig.json
。
これらのインポートされた構成ファイルにフィールドの競合がある場合、後でインポートされたフィールドが以前にインポートされたフィールドを上書きすることに注意してください。
具体的な詳細: https://github.com/microsoft/TypeScript/pull/50403
2-5. すべての列挙が結合列挙になる
TypeScript が最初に列挙型を設計したとき、列挙型は次のような同じ型の数値定数のセットにすぎませんでしたE
。
enum E {
Foo = 10,
Bar = 20,
}
E.Foo
,E.Bar
通常の変数と比較して、唯一特別なのは、 型の任意の変数に代入できることですE
。それ以外は、numbers
次のようなタイプとほとんど区別がつきません。
function takeValue(e: E) {
}
takeValue(E.Foo); // works
takeValue(123); // error!
TypeScript 2.0 で enum リテラル型が導入されるまで、列挙型は特別なものになりませんでした。Enum リテラル型は、各 enum メンバーに独自の型を与え、enum 自体を各メンバー型のコレクションに変換します。また、以下の列挙に示すように、列挙型のサブセットのみを参照し、それらの型の範囲を狭めることもできますColor
。
// Color is like a union of Red | Orange | Yellow | Green | Blue | Violet
enum Color {
Red, Orange, Yellow, Green, Blue, /* Indigo */, Violet
}
// Each enum member has its own type that we can refer to!
type PrimaryColor = Color.Red | Color.Green | Color.Blue;
function isPrimaryColor(c: Color): c is PrimaryColor {
// Narrowing literal types can catch bugs.
// TypeScript will error here because
// we'll end up comparing 'Color.Red' to 'Color.Green'.
// We meant to use ||, but accidentally wrote &&.
return c === Color.Red && c === Color.Green && c === Color.Blue;
}
また、一部のシナリオでは、たとえば列挙メンバーが関数呼び出しで初期化される場合、TypeScript は列挙値のセットを計算できず、結合列挙を放棄し、代わりに古い列挙戦略を使用します。
TypeScript 5.0 では、列挙型メンバーごとに一意の型を作成することで、すべての列挙型を共用体列挙型に変換することで、この問題を解決しています。このようにして、すべての場合において列挙値は共用体の列挙型になります。
具体的な詳細: https://github.com/microsoft/TypeScript/pull/50528
2-6.--moduleResolution
設定項目サポートbundler
オプション
TypeScript 4.7 では、--module
および設定項目におよびオプションが--moduleResolution
導入されています。これらのオプションは、Node.js の ECMAScript モジュールの検索ルールをよりよく模倣します。ただし、このパターンには多くの制限があります。たとえば、Node.js の ECMAScript モジュールでは、相対インポートにファイル拡張子を含める必要があります。node16
nodenext
// entry.mjs
import * as utils from "./utils"; // wrong - we need to include the file extension.
import * as utils from "./utils.mjs"; // works
しかし、フロントエンド技術の発展により、この検索ルールは時代遅れになりました。最新のパッケージ化ツールのほとんどは、Node.js の ECMAScript モジュールと CommonJS モジュールのルックアップ ルールを組み合わせて使用します。そのため、バンドル ツールの動作をエミュレートするために、TypeScript は新しい戦略を導入するようになりました--moduleResolution bundler
。
{
"compilerOptions": {
"target": "esnext",
"moduleResolution": "bundler"
}
}
Vite、esbuild、swc、Webpack、Parcel などのパッケージ化ツールを使用している場合、bundler
この構成は非常に適しています。
具体的な詳細: https://github.com/microsoft/TypeScript/pull/51669
2-7. サポートexport type *
TypeScript 3.8 で型 import/export が導入されたときexport _ from "module"
、export _ as xx from "module"
または のような型エクスポートは許可されませんでした。TypeScript 5.0 では、次の 2 つの型エクスポート構文のサポートが追加されました。
// models/vehicles.ts
export class Spaceship {
// ...
}
// models/index.ts
export type * as vehicles from "./vehicles";
// main.ts
import {
vehicles } from "./models";
function takeASpaceship(s: vehicles.Spaceship) {
// ok - `vehicles` only used in a type position
}
function makeASpaceship() {
return new vehicles.Spaceship();
// ^^^^^^^^
// 'vehicles' cannot be used as a value because it was exported using 'export type'.
}
詳細については、https: //github.com/microsoft/TypeScript/pull/52217を参照してください。