目指せ!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は昨今ではかなり人気でもあり、今後の開発の役に立つと思います。