この記事では、
@kintone/dts-gen
と
kintone JavaScript Client
を使って、TypeScriptでkintoneカスタマイズをする方法を説明します。
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/kintone-samples/sample-kintone-webpack-for-intermediate
git clone
またはリンク先右上の緑色のClone or downloadボタンからZipファイルをダウンロードして利用してください。
以降の導入方法は上記ページのReadmeを参照ください。
上記のコードは
目指せ中級者シリーズ
で使ったリポジトリと同じです。
すでに別の中級者シリーズを試したことがある人も、再度ディレクトリ直下でnpm install
を実行してください。
細かい設定内容は後述しますが、これでTypeScriptを利用するための必要パッケージがインストールされます。
kintone REST API Clientを使って、kintone REST APIを実行してみよう
で作成したカスタマイズを、TypeScriptで実装し直してみましょう。
今回は、上記の記事と同じアプリを使用します。
アプリを作っていない場合には、
アプリの用意と設定
を参考にアプリを作成してください。
コードを編集する前に、JavaScript APIの型にアクセスするための型定義を用意します。
@kintone/dts-gen
というライブラリを使うことで、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
|
コマンドが成功すると、次のような型定義ファイル(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
は見積アプリのレコードですよ」ということを定義できます。
これを
型アサーション
といいます。
4行目〜18行目:製品アプリの型定義(@kintone/rest-api-client用)
固定リンクがコピーされました
実は、
@kintone/rest-api-client
はTypeScriptをサポートしています。
ただし、@kintone/rest-api-clientの型定義は、@kintone/dts-genのようにコマンドから作成できません。
そのため、このように製品アプリの型を自身で用意する必要があります。
@kintone/rest-api-clientの型定義方法の詳細は
ドキュメント
を確認してください。
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
|
詳細は、
customize-uploaderを使って、複数のkintoneアプリにkintoneカスタマイズファイルを自動でアップロードしよう
を確認してください。
自動ファイルアップロードもできます。
TypeScript自体と、それに対応するkintoneのエコシステムが醸成されてきた結果、このようにかなりよい開発体験を得ることができるようになってきました。
kintoneを扱う上で完全には避けられない、フィールドコードの勘違いなど、大分減らせることでバグも未然に防ぐことができ、かなりTypeScriptでkintoneをカスタマイズするのは大分魅力的だと思っています。
今回の記事でTypeScriptに興味がでたら、ぜひ入門者用の書籍などを参考にして学んでみてください。TypeScriptは昨今ではかなり人気でもあり、今後の開発の役に立つと思います。