目指せ!JavaScript カスタマイズ中級者(5)〜TypeScript 編〜

目次

はじめに

今回は次の 2 つのツールを使って実践的な TypeScript コードを書きます。

TypeScript を使って JavaScript カスタマイズをする基本的な方法については、当サイトの別記事 TypeScript で kintone カスタマイズ開発をしてみよう で紹介しています。

目指せ中級者シリーズの記事一覧は以下を参照してください。
目指せ中級者シリーズ

TypeScript とは:TypeScript を使うメリット

TypeScript とは、Microsoft が開発したオープンソースのプログラミング言語で、JavaScript に「型」情報を追加できるようになっています。

「型」というのは変数などに格納される値が、数値なのか文字列なのかなど判別するために宣言します。
数値型や文字列型など基本的なもの以外にも、ユーザー独自の型も定義できます。
型情報のおかげで、扱おうとしているデータの中身が実行せずともコードを書くタイミングで明らかになるため、バグを起こしにくくなります。

たとえば、API から取得できる kintone の数値・計算フィールドの値は数字ではなく文字列ですが、それに直接乗算しようとすると、数値ではないのでエラーと判断されます。

次の例は、数値・計算フィールドにそのまま乗算しようとするとエラーになる例です。
計算フィールド「合計金額」に record.合計金額.value * 0.1 で乗算しようとしてエラーが表示されています。

他にも、あるオブジェクトの中に該当のキーがない場合など、アクセスしようとすると IDE がエラーと判断します。

次の例は、存在しないオブジェクトのプロパティにアクセスしようとした際の IDE のエラーです。
オブジェクト record.文字列.value に値を代入しようとしたものの、実際には「文字列」というフィールドコードは存在しないためエラーが表示されています。

TypeScript で、kintone のアプリの各フィールドの型情報を用意し、それを利用することで上記のように kintone のフィールドコードを間違えたりせずに書くことができます。
特にテーブルの階層が深い複雑な構造や、REST API のリクエストパラメーターやレスポンスに対して大きな効果を発揮します。

実際にやるとどうなるのか、サンプルを試してみましょう。

準備

コード: https://github.com/cybozudevnet/sample-kintone-webpack-for-intermediate (External link)

git clone またはリンク先右上の緑色の Clone or download ボタンから Zip ファイルをダウンロードして利用してください。
以降の導入方法は上記ページの Readme を参照ください。

上記コードの URL 自体は 目指せ中級者!実践 JavaScript カスタマイズレベルアップ(1)〜webpack 編〜 から(4)までものと同一です。
前回までの記事をお試しいただいた方も念の為再度ディレクトリー直下で npm install をやっておいてください。

細かい設定内容は後述しますが、これで TypeScript を利用するための必要パッケージがインストールされます。

サンプル

第 4 回の記事 と同様のサンプルを使って、TypeScript ならどう書くかという感じでコードを書いてみたいと思います。
第 4 回の記事と同様のアプリを利用するので上記記事の「アプリの用意と設定」のようにアプリを用意してから以下お試しください。

型情報の取得

コードを編集する前に、JavaScript API の型にアクセスするための型定義を用意します。
@kintone/dts-gen (External link) というライブラリを使うことで、JavaScriptAPI で扱うための型定義をアプリから取得できます。
下記コマンドを実行し、見積アプリから型情報を取得しておきます。
作成されたものを型定義ファイルといいます。

型定義ファイルは、サンプルにすでにはいっていますが、次のコマンドを実行することでお使いの環境のアプリの定義で上書きされます。

1
npx @kintone/dts-gen --base-url https://kintoneのドメイン.cybozu.com -u ユーザー名 -p パスワード --app-id アプリID --type-name Quote --namespace KintoneTypes -o src/types/Quote.d.ts
information

今回のサンプルコードのケースでは、見積アプリと商品アプリの 2 アプリを利用します。
見積アプリのみ JavaScriptAPI で値を書き換えるため、見積アプリの型定義ファイルを @kintone/dts-gen で生成します。
商品アプリは REST API で書き換えるため、別途サンプルコード内に型を定義する必要があります。

コマンドが成功すると、次のような型定義ファイル(src/types/Quote.d.ts)が生成されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
declare namespace KintoneTypes {
  interface Quote {
    No: kintone.fieldTypes.SingleLineText;
    文字列__1行_: kintone.fieldTypes.SingleLineText;
    文字列__複数行_: kintone.fieldTypes.MultiLineText;
    日付: kintone.fieldTypes.Date;
    合計金額: kintone.fieldTypes.Calc;
    見積明細: {
      type: "SUBTABLE";
      value: {
        id: string;
        value: {
          単価: kintone.fieldTypes.Number;
          数量: kintone.fieldTypes.Number;
          型番: kintone.fieldTypes.SingleLineText;
          商品名: kintone.fieldTypes.SingleLineText;
          小計: kintone.fieldTypes.Calc;
        };
      }[];
    };
  }
  interface SavedQuote extends Quote {
    $id: kintone.fieldTypes.Id;
    $revision: kintone.fieldTypes.Revision;
    更新者: kintone.fieldTypes.Modifier;
    作成者: kintone.fieldTypes.Creator;
    レコード番号: kintone.fieldTypes.RecordNumber;
    更新日時: kintone.fieldTypes.UpdatedTime;
    作成日時: kintone.fieldTypes.CreatedTime;
  }
}

サンプルコード

生成された型定義ファイルを使って、TypeScript で kintone カスタマイズをしてみましょう。
ダウンロードしたソースコードの src/apps/quote_ts/index.ts を確認してください。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import {KintoneRestAPIClient, KintoneRecordField} from '@kintone/rest-api-client';

// 製品アプリの型を定義
type SavedProduct = {
  $id: KintoneRecordField.ID;
  $revision: KintoneRecordField.Revision;
  更新者: KintoneRecordField.Modifier;
  作成者: KintoneRecordField.Creator;
  レコード番号: KintoneRecordField.RecordNumber;
  更新日時: KintoneRecordField.UpdatedTime;
  作成日時: KintoneRecordField.CreatedTime;
  ラジオボタン: KintoneRecordField.RadioButton;
  文字列__複数行__0: KintoneRecordField.MultiLineText;
  型番: KintoneRecordField.SingleLineText;
  商品名: KintoneRecordField.SingleLineText;
  数値: KintoneRecordField.Number;
  在庫数: KintoneRecordField.Number;
}

// 商品アプリのアプリIDを入力してください
const productsAppId = 122;

const events = ['app.record.create.submit', 'app.record.edit.submit'];

kintone.events.on(events, async (event) => {
  const record = event.record as KintoneTypes.Quote;

  // kintoneへ接続するためのインスタンスを作成
  const client = new KintoneRestAPIClient({});

  // 今回はコード簡略化のために、テーブルの商品は重複禁止とします。
  // ただの簡易的な重複チェックなので意味は理解しなくてOKです。
  const hasDuplicatedRow = record.見積明細.value.some((rowA, indexA, arr) => {
    return arr.find(
      (rowB, indexB) =>
        indexA !== indexB && rowA.value.型番.value === rowB.value.型番.value
    );
  });
  if (hasDuplicatedRow) {
    event.error = '重複した商品は登録できません。';
    return event;
  }

  // テーブルに入っている商品レコードを取得
  let products;
  try {
    // Genericに型を指定することで, products変数を利用する際に型推論ができる
    products = await client.record.getRecords<SavedProduct>({
      app: productsAppId,
      query: `型番 in (${record.見積明細.value
        .map((row) => `"${row.value.型番.value}"`)
        .join(', ')})`,
    });
  } catch (error) {
    event.error = 'レコードの取得に失敗しました';
    return event;
  }

  // 商品リストの在庫数を差し引いたデータを作成
  const deductedProductRecords = products.records.map((productRecord) => {
    const tableRow = record.見積明細.value.find(
      (row) => productRecord.型番.value === row.value.型番.value
    );

    // アップデートのキーとなる型番と, 差し引いた在庫数を格納する。
    return {
      型番: {
        value: productRecord.型番.value,
      },
      在庫数: {
        value:
          Number(productRecord.在庫数.value) -
          Number(tableRow?.value.数量.value),
      },
    };
  });

  // 在庫数を差し引いたあと在庫数が0未満になるようなレコードがないか確認
  const noStockRecords = deductedProductRecords.filter(
    (productRecord) => Number(productRecord.在庫数.value) < 0
  );

  // 差し引き1未満のレコードがでた場合はエラーとみなしレコードの作成をストップさせる
  if (noStockRecords.length > 0) {
    // event.errorにデータをいれたあとeventを返すとレコードの作成をストップできる
    // どの商品が問題か示すために在庫が足りない商品の型番を列挙する
    event.error = `在庫がない商品があります。型番 ${noStockRecords
      .map((productRecord) => productRecord.型番.value)
      .join(', ')}`;

    return event;
  }

  // 問題なければアップデート
  try {
    await client.record.updateRecords({
      app: productsAppId,
      records: deductedProductRecords.map((productRecord) => {
        return {
          updateKey: {
            field: '型番',
            value: productRecord.型番.value,
          },
          record: {
            在庫数: {
              value: productRecord.在庫数.value,
            },
          },
        };
      }),
    });
  } catch (error) {
    event.error = `アップデートに失敗しました。${error.message}`;
    return event;
  }

  return event;
});

実際にコードを編集してみて、27 行目あたりで record の中身をみようとするとどうなるか、Visual Studio Code の挙動を試してみてください。
下記画像のように、record. と入力していくと見積アプリのフィールドに基づいたサジェストがされるはずです。

サンプルコードの説明

ここでは TypeScript のすべては説明できませんが、サンプルコードの概要をかいつまんで紹介します。
実際には、第 4 回で紹介しているコードとはあまり差分はありません。次に示す型情報の扱いのみ違いがあります。

26 行目:event.record の型情報を付与

1
const record = event.record as kintoneTypes.Quote;

とかかれた箇所ですが、これは先述の @kintone/dts-gen で作成した型定義を当てています。こうすることで、「event.record は見積アプリのレコードですよ」ということを定義できます。
これを 型アサーション (External link) といいます。

4 行目〜18 行目:製品アプリの型定義(@kintone/rest-api-client 用)

実は、 @kintone/rest-api-client (External link) は TypeScript をサポートしています。
ただし、@kintone/rest-api-client の型定義は、@kintone/dts-gen のようにコマンドから作成できません。
そのため、このように製品アプリの型を自身で用意する必要があります。
@kintone/rest-api-client の型定義方法の詳細は ドキュメント (External link) を確認してください。

48 行目: @kintone/rest-api-client に型情報を渡す

1
2
3
products = await client.record.getRecords<SavedProduct>({
  // 処理
});

としている行ですが、4 行目~18 行目で定義した製品アプリの型情報(SavedProduct)を渡すことで、getRecords() で返ってくるレコードは、SavedProduct 型ですよと教えています。
これにより、REST API から返却されたレコードについてもサジェストされるようになります。

サンプルコードのビルド

ブラウザーに TypeScript を直接動作させることはできませんが、TypeScript→JavaScript に変換できるように webpack の設定を追記しています。
ビルドコマンドを打つことで、JavaScript に変換できるので、それをアップロードします。

1
npx webpack --mode production

詳細は、 目指せ中級者!実践 JavaScript カスタマイズレベルアップ(3)〜自動で一括ファイルアップロード編〜 を確認してください。
自動ファイルアップロードもできます。

おわりに

TypeScript 自体と、それに対応する kintone のエコシステムが醸成されてきた結果、このようにかなりよい開発体験を得ることができるようになってきました。
kintone を扱う上で完全には避けられない、フィールドコードの勘違いなど、大分減らせることでバグも未然に防ぐことができ、かなり TypeScript で kintone をカスタマイズするのは大分魅力的だと思っています。

今回の記事で TypeScript に興味がでたら、ぜひ入門者用の書籍などを参考にして学んでみてください。TypeScript は昨今ではかなり人気でもあり、今後の開発の役に立つと思います。