オリジナル記事
How to Use TypeScript with Salesforce – Part 1
TypeScript(TS)への支持はこの数年で高まりを見せています。
2022年3月29日
TypeScript(TS)への支持はこの数年で高まりを見せています。TypeScriptはLightning Webコンポーネント(LWC)とともに使用できることをご存じでしょうか。TypeScriptに対応するSalesforceの製品および機能は、Salesforce機能、Lightning Web Runtimeなどその他にもいくつかあります。
2回にわたってお届けするブログの第1回にあたる今回は、TypeScriptの概要やインストール方法、プロジェクトでの利用方法の他、TypeScriptで作成できるさまざまなデータ型についてご紹介します。なお第2回では、SalesforceエコシステムのさまざまなポイントでTypeScriptを利用できることをご説明する予定です。
TypeScriptとは
TypeScriptとは、型指定が厳密なJavaScript(JS)です。JavaScriptのすべての機能を利用することが可能で、TypeScriptの型システムという追加レイヤーを最上部に提供します。TypeScript自体を実行することはできませんが、TypeScriptのコードが標準ベースのJavaScriptへのコンパイルを行います。
JavaScriptとの違い
JavaScriptは動的型付け言語で、ビジネスロジックが意図したとおりに動作することを検証するために実行されます。たとえば、型強制が起こると、予想とは異なるアウトプットが出る場合があります。一方、TypeScriptは静的型付けなどの機能を提供します。また、コンパイル時のエラーを検出できるコンパイラーも提供されますが、TypeScript以外の言語では、このようなエラーはランタイム中にポップアップで表示されます。検出されるエラーには、型のエラー、スペルミス、メソッドの使用方法の間違い、呼び出されていない機能、基本的なロジックエラー、非例外的なエラー、NULL、undefinedのチェックなどがあります。TypeScriptは、ジェネリクス、インターフェース、列挙型などの機能も提供し、適切な設計のサポートや、開発時およびコードリファクタリング時のガイド提供を実現します。今回のブログでは、このような機能についても後ほどご紹介します。
TypeScriptのインストールとコンパイル
TypeScriptのコンパイラーはあらゆるプロジェクトにインストール可能なnpmパッケージとして利用できます。インストールを完了すると、TypeScriptファイル(*.ts)を作成できるようになります。このコンパイラーは、.tsファイルを標準的な.jsファイルにコンパイルします。これは、特定のバージョンのECMAScript(ES5、ES6など)をターゲットするように設定できます。コンパイラーを使用するには、package.jsonファイルのスクリプトセクションに、新しいスクリプトを追加する必要があります。
1"scripts": {
2 ...
3 "ts-build": "tsc"
4 ...
5}tsconfig.jsonファイルまたはjsconfig.jsonファイルにより、TypeScriptおよびプロジェクトのコンパイラーの動作を制御できます。利用できる各種設定オプションについては、ドキュメントをご覧ください。
コンパイルプロセスでは、型情報の大半が取り除かれます。なおいくつかの例外については、このブログの後半でご紹介します。以下は、所定のTypeScriptコードに対してコンパイルされたJavaScriptコードの例です。
ただ、注意すべきは、TypeScriptのコンパイラーはランタイムの動作やJavaScriptコードのロジックは修正しないということです。
こちらのTypeScriptコードを例に確認してみましょう。ここには、送信された値を記録するprintValue機能が含まれています。このような型のvalパラメーターは、stringに設定されています。このコードスニペットでは、ストリングにブール値を割り当てようとしたために、Line 6にコンパイル時のエラーが表示されています。
しかし、コンパイル完了後のJavaScriptファイルの結果はこのようになります。valパラメーターをstring値のみに割り当てる追加コードは生成されません。したがって、Line 6はランタイム時にエラーが発生することなく実行されます。
もしかすると、コンパイル時のエラーがある場合、JavaScriptファイルはどのように生成されるのかという疑問をお持ちかもしれません。これはコンパイラーのデフォルトの動作で、型のエラーがある場合でもJavaScriptファイルを生成します。これを防ぐために、実行時に–noEmitOnErrorフラグを設定することができます(こちらのドキュメントをご参照ください)。
VS CodeなどのTypeScriptプラグインを備えるエディターを使用している場合、型のエラー検出のためのファイルのコンパイルは必須ではありません。VS CodeはTypeScript言語サービス経由で標準のTypeScriptサポートを提供しており、タイピングの完了時に提案を行ったり、エラーをハイライトしたりしてくれます。これは、TypeScriptをプロジェクトに段階的に導入しようとする場合には非常に有用です。
厳密さのレベルはさまざま
既存のJavaScriptプロジェクトは、五月雨式にTypeScriptに移行することができます。つまり、*.jsファイルから*.tsへの変換を一度に行う必要はありません。しかしその場合でも、既存のJavaScriptプロジェクトでTypeScriptの型システムのメリットを享受することは可能です。以下の図は、TypeScriptを導入する際の5つの段階を示しています。段階が進むごとに、型の検証はより厳密になります。
はじめの3つの段階では、JavaScriptファイル上で作業を進めますが、TypeScriptコンパイラーやVS CodeのTypeScript言語サービスを使ってエラーを検出することができます。しかし、JSファイルでの型のチェックは、若干動作が異なります。詳細についてはこちらのガイドを参照してください。
一部のJavaScriptファイルで型のチェックを有効にするには、チェックしたいファイルの最初に//@ts-checkを追加します。プロジェクト内のファイルをすべてチェックする場合は、プロパティcheckJsとallowJsをtsconfig.jsonファイルに追加するか、コンパイラーにフラグとして渡します。
はじめの2つの段階では、TypeScriptは使用状況やポイントエラーにもとづき、データ型の推論を実行しようとします。VS Codeを使用している場合は、コンパイラーを実行しなくても、[Problems(問題)]タブにエラーが表示されます。
次の段階では、JSDocの論理構成から変数のデータ型を指定することで、型を明示的に実行できます。
1// The below JS Docs construct indicates that the variable is of type number.
2
3/** @type {number} */ let a = 2; a = 'value'; // Error: Type string is not assignable to number問題がなければ、TypeScriptファイルの作成を開始することで、型システムのメリットをすべて利用できます。
TypeScriptとの連携
以下では、いくつかの重要な概念に絞って簡単なサマリーをご紹介します。なお、TypeScriptの公式ドキュメントでは詳細な説明を確認できます。
型とインターフェース
機能の変数、入力値、出力値などには、標準のデータ型から任意のものを割り当てることができます。型のチェックのエラーを引き起こす特定の値を必要としない場合は、特別な型であるany使用できます。
1// Declaring a variable of type "number".
2let count: number;
3
4// A function that takes in a "recordId" parameter of type "string" // and returns a value of type "boolean" function isValidRecordId(recordId: string): boolean { return true; }2つ以上の型を組み合わせて、union型を作成することもできます。簡単な例を以下に挙げます。
1// A function that returns a value of either "string" or "number" types
2function getRecordId(name: string): string | number {
3 return 'someid';
4}型エイリアスを作成することで、複数の場所で型の定義を再利用することが可能です。
1// A type alias "ID" to store a union type
2type ID = string | number;
3
4// Declaring a variable of type alias "ID"
5let someId: ID;
6
7// A function that returns a value of type alias "ID"
8function getRecordId(name: string): ID {
9 return 'someid';
10}オブジェクトなど、プリミティブではない型を定義することも可能です。オブジェクト種別を定義するには、いくつかの方法があります(すなわち、オブジェクトのシェイプ)まず、インラインで定義します。
1// The structure of the "acc" param is defined inline
2function getAccountName(acc: {id: string, name: string, createdDate: Date}): string{
3 return acc.name;
4}次に、型エイリアスを作成します。
1// A Type Alias called Account that stores the object shape
2type Account = {
3 id: string
4 name: string
5 createdDate: Date
6}
7
8function getAccountName(acc: Account): string {
9 ...
10}最後に、インターフェースを作成します。型エイリアスとインターフェースの唯一の違いは、インターフェースには新しいプロパティを追加できる一方、型エイリアスには追加できないという点です。
1// An interface called Account that stores the object shape
2interface Account {
3 id: string
4 name: string
5 createdDate: Date
6}
7
8// You can add new properties to an interface
9interface Account {
10 lastModifiedDate: Date
11}
12
13function getAccountName(acc: Account): string {
14 ...
15}ジェネリクス
さまざまなデータ型と連携可能な機能やクラスを作成することは一般的ですが、any型を使っても型の安全性が確保されるわけではありません。そこで重要な役割を担うのがジェネリクスです。ジェネリクスにより、機能を呼び出したりクラスを初期化したりする場合、値とともに引数として型を渡すことができます。
以下の例では、機能queryを作成し、SOQLクエリをパラメーターとして取り入れ、結果を返しています。結果の型は、クエリしているオブジェクトにもとづいているのは明らかです。このようにして、ジェネリクスによりquery機能の型安全性を確保します。
1// First we define the different types the query function supports
2type Account {
3 Id: string
4 Name: string
5}
6
7type Contact {
8 Id: string
9 FirstName: string
10 LastName: string
11}
12
13// Next, we define the query function.
14// We use the "Type", a special kind of variable that works on types rather than values
15// The value to this variable is passed when the function is being invoked
16function query<Type>(soqlQuery: string): Type[] {
17 ...
18 return results;
19}
20
21// Finally, we call the query function
22// We pass the Type information using angular braces < >
23// accountResults is of Type "Account"
24let accountResults = query<Account>("select Id, Name from Account");
25
26// contactResults is of Type "Contact"
27let contactResults = query<Contact>("select Id, FirstName, LastName from Contact");Salesforce CLIプラグインの構築時には、ジェネリクスが動作しているのを確認できます。この点については、シリーズのパート2で解説します。
列挙型
列挙型は、TypeScriptが提供する機能の1つです。その目的は、型安全性だけではありません。Apex同様、列挙型も指定された定数のセットを定義することができます。定数を特定の値に初期化したり、デフォルトのオートインクリメントされた値にしたりすることもできます。他の型の定義とは異なり、列挙型はコンパイル時に最終JavaScriptコードから削除されず、ランタイムでオブジェクトのように動作します。一方、const列挙型はコンパイル時に完全に削除されます。
1// Defining an Enum
2// Since all constants are uninitialized, they default to auto-incremented numbers
3enum Status{
4 Success,
5 Failure
6}
7
8console.log(Status.Success); // Prints 0
9console.log(Status.Failure); // Prints 1
10
11// Defining another Enum, where we initialize the values
12enum AccessLevel{
13 ReadOnly = "ReadOnly",
14 ReadWrite = "ReadWrite",
15}
16
17console.log(AccessLevel.ReadOnly); // Prints "ReadOnly"モジュール
モジュールの概念は、JavaScriptとTypeScriptで共通です。TypeScriptでは、エイリアスとインターフェースのように、型の宣言のエクスポートとインポートも可能です。
1// shared.ts file
2
3// A type declaration marked with "export" so that it can be imported in another file
4export interface Contact {
5 id: string,
6 FirstName?: string,
7 LastName: string
8}
9
10...
11
12// A function marked with "export" so that it can be imported in another file
13export function sayHello(obj: Contact | Lead){
14 return `Hello ${obj.FirstName} ${obj.LastName}`;
15}1// demo.ts file
2
3// Importing the exported function and type declaration (interface)
4import { Contact, sayHello } from './shared';
5
6// Using the imported interface
7let con: Contact = {id: '1', FirstName: 'John', LastName: 'Doe'};
8
9// Using the imported function
10console.log(sayHello(con));TypeScriptはアンビエントモジュールの宣言により、TypeScriptに記述されないライブラリのシェイプの指定も可能です。たとえば、以下はJavaScriptで記述されたライブラリ(sf-libs)で、いくつかの機能を持っています。
1// sf-libs.js
2function convertTo15Digit(id){
3 return id.substr(0,15);
4}
5...
6export {convertTo15Digit};このライブラリをTypeScriptファイル内にインポートすると、型のエラーが生じます。これは、このモジュールと機能で利用できる型の情報がないためです。
1import { convertTo15Digit, ... } from "sf-libs";
2// Error: Cannot find module "sf-libs" or its corresponding type declarations.以下は、sf-libs JavaScriptモジュールのTypeScriptのアンビエントモジュールです。アンビエントモジュールはモジュールのシェイプを定義するだけで、実装の詳細は含まないことに注意しましょう。
1// sf-libs.d.ts
2declare module "sf-libs" {
3 function convertTo15Digit(id: string): string;
4 ...
5}Lightning Webコンポーネントコアモジュールについて、アンビエントモジュールの宣言をご確認ください。
まとめ
今回のブログでは、TypeScriptをプロジェクトで活用し、段階的に導入を進める方法について、概要をご紹介しました。TypeScriptの詳細は以下のリソースでご確認いただけます。
このシリーズの第2回では、SalesforceエコシステムのさまざまなポイントでTypeScriptを利用できることをご説明する予定です。
著者紹介
Aditya Naag Topalliは、Salesforceの14x Certified Lead Developer Advocateです。動画やウェブセミナー、ブログ、オープンソースへの貢献を通じ、Salesforceエコシステム内外の開発者のスキル強化や指導に取り組んでいます。世界中のカンファレンスやイベントでの講演も頻繁に行っています。AdityaのTwitterやLinkedInのアカウントをぜひフォローしてください。GitHubでの取り組みもご覧ください。