JavaScriptの便利な書き方
はじめに
基本的に、cybozu developer networkのAPIドキュメントのサンプルコードやチュートリアルなどのJavaScriptは、ECMA Script6以降のバージョンで書かれています。
今回はECMA Script5からの変更点や、kintoneのJavaScriptカスタマイズでの使い所を紹介します。
変数宣言をconst / letにして安全に初期化する
var abc = 123;
のように、ECMA Script5以前での変数宣言ではvar
を使っていましたが、今後はconst
とlet
を使っていくことをおすすめします。
-
var
は関数スコープで、const
とlet
はブロックスコープです。スコープとは変数の名前や関数などの参照できる範囲を決めるものです。
スコープの中で定義された変数はスコープの内側でのみ参照でき、スコープの外側からは参照できません。
var
はスコープが広く、関数スコープまでです。
const
とlet
はブロックスコープといって、ifブロックなど{}
の中の範囲で有効になります。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
// 関数内で変数宣言する function someFunc() { var var_func_number = 1000; const const_func_number = 2000; let let_func_number = 3000; } // typeofで宣言済みか調べる(宣言済みの場合trueと表示される) // 下記はすべて関数の外から参照できないのでfalseと表示される console.log('var: ', typeof var_func_number !== 'undefined'); console.log('const: ', typeof const_func_number !== 'undefined'); console.log('let: ', typeof let_func_number !== 'undefined'); // ifブロック内で変数宣言する if (true) { var var_block_number = 1000; const const_block_price = 2000; let let_block_price = 3000; } // typeofで宣言済みか調べる(宣言済みの場合trueと表示される) // varで宣言するとブロック外からも参照できてしまう console.log('var: ', typeof var_block_number !== 'undefined'); // const / letで宣言したものはブロック外から参照できない console.log('const: ', typeof const_block_number !== 'undefined'); console.log('let: ', typeof let_block_number !== 'undefined');
-
const
は再代入と再宣言ができません。この変数は代入し直さない、とわかっている場合は
const
を使いましょう。
長いコードになってくると、同じ変数名を間違って再度宣言したり、意図せず再利用してしまうこともありますが、積極的にconst
を使うとそれを防げます。
ただし、Objectや配列の中身は書き換えることができますのでご注意ください。1 2 3 4 5 6 7
var var_price = 1000; var_price = 2000; // varで宣言した変数は書き換えてもエラーにならない var var_price = 3000; // 再宣言もできてしまう const const_price = 1000; const_price = 2000; // constで宣言した変数は書き換えるとエラーになる const const_price = 3000; // constは再宣言もできない
-
let
は再代入が可能です。(const
と同様再宣言は不可)let
はconst
同様に再宣言できませんが、再代入はできます。
値を途中で書き換えることがわかっている場合はlet
で変数を宣言しましょう。
基本的にはconst
を使い、明らかに再代入する場合はlet
を使う、という風に使い分けていくといいと思います。1 2 3 4 5 6 7
var var_price = 1000; var_price = 2000; // varで宣言した変数は書き換えてもエラーにならない var var_price = 3000; // 再宣言もできてしまう let let_price = 1000; let_price = 2000; // letで宣言した変数は再代入が可能 let let_price = 3000; // letは再宣言できない
for文の使用を必要最低限にする
たとえばレコード配列のデータなどを繰り返し処理をするケースは多々ありますが、それをforでやると無限ループさせてしまったり、記述が長くなってしまったりしてしまいます。
そのため、配列を扱うときはなるべく次に紹介するforEach()
/ filter()
/ map()
/ reduce()
を使ってみるようにしましょう。
forEach()
単純にすべてのレコードのデータを参照したい場合はforEachを使うのが適しています。
詳細は次の記事を参考にしてください。
Array.prototype.forEach()
例)会社名一覧をconsole.logに表示
-
forで記述した場合
弱点として、レコードの数の指定を間違えると無限ループに陥ってしまいます。 ループを回すには
records[i]
と書く必要があり、i
に代入される数字を常に意識せざるをえません。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
const records = [ { $id: { value: 1, type: 'RECORD_NUMBER', }, '会社名': { value: 'ABC株式会社', type: 'SINGLE_LINE_TEXT', }, }, { $id: { value: 2, type: 'RECORD_NUMBER', }, '会社名': { value: 'サンプル株式会社', type: 'SINGLE_LINE_TEXT', }, }, { $id: { value: 3, type: 'RECORD_NUMBER', }, '会社名': { value: '株式会社XYZ', type: 'SINGLE_LINE_TEXT', }, }, ]; for (let i = 0; i < records.length; i++) { console.log(records[i]['会社名'].value); // 結果表示 }
-
forEachで記述した場合
わざわざ、何行目かを意識せずとも、
record
変数にrecords
のデータが1つずつ入って繰り返し処理を行ってくれますので見通しもよく便利です。
今までforでやっていることは、たいていこのように実装できるはずですので、まずforEachで実装できないか考えてみましょう。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
const records = [ { $id: { value: 1, type: 'RECORD_NUMBER', }, '会社名': { value: 'ABC株式会社', type: 'SINGLE_LINE_TEXT', }, }, { $id: { value: 2, type: 'RECORD_NUMBER', }, '会社名': { value: 'サンプル株式会社', type: 'SINGLE_LINE_TEXT', }, }, { $id: { value: 3, type: 'RECORD_NUMBER', }, '会社名': { value: '株式会社XYZ', type: 'SINGLE_LINE_TEXT', }, }, ]; records.forEach(function(record) { console.log(record['会社名'].value); // 結果表示 });
filter()
filterを使えば条件に一致した配列を取得できます。
たとえば、売上n円以上のデータだけが欲しい、ということを簡単に書くことができます。
詳細は次の記事を参考にしてください。
Array.prototype.filter()
例)小計が10,000円以上のレコードのみを求める
-
forで記述した場合
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
const records = [ { $id: { value: 1, type: 'RECORD_NUMBER', }, '小計': { value: '1000', type: 'NUMBER', }, }, { $id: { value: 2, type: 'RECORD_NUMBER', }, '小計': { value: '20000', type: 'NUMBER', }, }, { $id: { value: 3, type: 'RECORD_NUMBER', }, '小計': { value: '30000', type: 'NUMBER', }, }, ]; let results = []; for (let i = 0; i < records.length; i++) { if (records[i]['小計'].value > 10000) { results.push(records[i]); } } console.log(results); // 結果表示
-
filterで記述した場合
if文を使わずとも任意のデータが取得できるので見た目もすっきりします。
実際には、この程度の条件ならkintone APIを呼ぶときに指定すればいいですが、もうちょっと複雑な条件を指定したいときには重宝します。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
const records = [ { $id: { value: 1, type: 'RECORD_NUMBER', }, '小計': { value: '1000', type: 'NUMBER', }, }, { $id: { value: 2, type: 'RECORD_NUMBER', }, '小計': { value: '20000', type: 'NUMBER', }, }, { $id: { value: 3, type: 'RECORD_NUMBER', }, '小計': { value: '30000', type: 'NUMBER', }, }, ]; const results = records.filter(function(record) { return (record['小計'].value > 10000); }); console.log(results); // 結果表示
map
mapを使えば、配列すべてに処理を行えます(新しい配列が返却されます)。
詳細は次の記事を参考にしてください。
Array.prototype.map()
例)小計を求める(消費税込)
-
forで記述した場合
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
const records = [ { $id: { value: 1, type: 'RECORD_NUMBER', }, '小計': { value: '0', type: 'NUMBER', }, '単価': { value: '1000', type: 'NUMBER', }, 'ユーザー数': { value: '2', type: 'NUMBER', }, }, { $id: { value: 2, type: 'RECORD_NUMBER', }, '小計': { value: '0', type: 'NUMBER', }, '単価': { value: '3000', type: 'NUMBER', }, 'ユーザー数': { value: '4', type: 'NUMBER', }, }, { $id: { value: 3, type: 'RECORD_NUMBER', }, '小計': { value: '0', type: 'NUMBER', }, '単価': { value: '20000', type: 'NUMBER', }, 'ユーザー数': { value: '8', type: 'NUMBER', }, }, ]; let results = []; for (let i = 0; i < records.length; i++) { results.push(records[i]['小計'].value = records[i]['単価'].value * records[i]['ユーザー数'].value * 1.08); }
-
mapで記述した場合
これもタイプ数は大分減らせることができます。
records[i]
から始まらない分、短くてすみますし、バグを引き起こす可能性を低くできます。
実際には、小計を出す程度だと自動計算フォームを使えばいいですが、それではできない複雑な計算をする場合に使えます。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
const records = [ { $id: { value: 1, type: 'RECORD_NUMBER', }, '小計': { value: '0', type: 'NUMBER', }, '単価': { value: '1000', type: 'NUMBER', }, 'ユーザー数': { value: '2', type: 'NUMBER', }, }, { $id: { value: 2, type: 'RECORD_NUMBER', }, '小計': { value: '0', type: 'NUMBER', }, '単価': { value: '3000', type: 'NUMBER', }, 'ユーザー数': { value: '4', type: 'NUMBER', }, }, { $id: { value: 3, type: 'RECORD_NUMBER', }, '小計': { value: '0', type: 'NUMBER', }, '単価': { value: '20000', type: 'NUMBER', }, 'ユーザー数': { value: '8', type: 'NUMBER', }, }, ]; const results = records.map(function(record) { record['小計'].value = record['単価'].value * record['ユーザー数'].value * 1.08; return record; }); console.log(results); // 結果出力
reduce
reduceを使うとrecords
配列のすべての合計値などを求めることができます。
reduceは上記に挙げたものと比べると少し難しく見えるかもしれませんが、慣れてしまえば簡単にデータの合計値などを求めることができます。
reduceは左から右に配列を処理しますが、逆のreduceRightもあります。
例)すべての小計の合計を求める
-
forで記述した場合
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
const records = [ { $id: { value: 1, type: 'RECORD_NUMBER', }, '小計': { value: '10000', type: 'NUMBER', }, }, { $id: { value: 2, type: 'RECORD_NUMBER', }, '小計': { value: '20000', type: 'NUMBER', }, }, { $id: { value: 3, type: 'RECORD_NUMBER', }, '小計': { value: '30000', type: 'NUMBER', }, }, ]; var results = 0; for (var i = 0; i < records.length; i++) { results += Number(records[i]['小計'].value); } console.log(results); // 結果出力
-
reduceで記述した場合
タイプ数はほとんど変わりませんので違いがわかりにくいですが、やはり
index
変数i
を使わない分バグの確率は減ります。
forEachでも実装はできますが、こちらはresults
の初期化も不要ですし、reduce内で処理が完結するのでまとまりがあります。
例のように合計値を求めたり、records
配列をloopしてデータを計算したいときは積極的につかっていきたい関数です。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
const records = [ { $id: { value: 1, type: 'RECORD_NUMBER', }, '小計': { value: '10000', type: 'NUMBER', }, }, { $id: { value: 2, type: 'RECORD_NUMBER', }, '小計': { value: '20000', type: 'NUMBER', }, }, { $id: { value: 3, type: 'RECORD_NUMBER', }, '小計': { value: '30000', type: 'NUMBER', }, }, ]; var results = records.reduce(function(prev, record) { return prev + Number(record['小計'].value); }, 0); console.log(results); // 結果出力
Arrow関数を使ってシンプルに関数を記述する
関数の宣言には次の種類があります。
-
今までの関数宣言(通常のfunction式)
1 2 3 4 5 6 7 8
function someFunc() { // 処理内容 }; // もしくは var someFunc = function() { // 処理内容 }
-
Arrow関数
1 2 3
const someFunc = () => { // 処理内容 };
ES6からは上記Arrow関数が使えるようになりました。
=>
この形が矢印に見えるのでArrow関数と呼ばれています。
Arrow関数のメリット
Arrow関数にはいくつかメリットがあります。
-
短くかける。
上記のように関数宣言しているだけだと、ほとんど差はありません。
ですが、Callback関数など関数を多様するときにかなりスマートにかけます。
とあるレコード一覧のデータをArray.map()
やfilter()
を使って必要なものだけとりだし、それぞれ処理をするパターンをみていきましょう。Array.prototype.filter()
関数で税込にするべきレコードか判別し、その後Array.prototype.map()
で税込にした金額を配列で取得する場合を考えてみます。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
const records = [ { $id: { value: 1, type: 'RECORD_NUMBER', }, '税種別': { value: '税込', type: 'RADIO_BUTTON', }, '小計': { value: '10000', type: 'NUMBER', }, }, { $id: { value: 2, type: 'RECORD_NUMBER', }, '税種別': { value: '税抜', type: 'RADIO_BUTTON', }, '小計': { value: '20000', type: 'NUMBER', }, }, { $id: { value: 3, type: 'RECORD_NUMBER', }, '税種別': { value: '税込', type: 'RADIO_BUTTON', }, '小計': { value: '30000', type: 'NUMBER', }, }, ]; // 税種別に[税込]か[税抜]が入り // 小計フィールドに金額が入るものとします。 // 今までの関数宣言の場合 const includedTaxPriceArray = records.filter(function(record) { return record['税種別'].value === '税込'; }).map(function(record) { return record['小計'].value * 1.1; }); // Arrow関数の場合 const includedTaxPriceArrayWithArrowFunc = records.filter((record) => { return record['税種別'].value === '税込'; }).map((record) => { return record['小計'].value * 1.1; }); console.log(includedTaxPriceArray); console.log(includedTaxPriceArrayWithArrowFunc)
このように
function
がなくなっただけでもスッキリしますね。
また、さらにルールがあって、引数が1つなら引数の括弧が省略できる、1行で結果を返せるならreturn
を省略できるというルールがあり、それを適用すると次のようにもっと短くかけます。1 2
// Arrow関数(括弧Return省略版) const includedTaxPriceArrayWithArrowFunc = records.filter(record => record['税種別'].value === '税込').map(record => record.price.value * 1.1);
この書き方を利用して書き換えた場合は、次のようになります。
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
const records = [ { $id: { value: 1, type: 'RECORD_NUMBER', }, '税種別': { value: '税込', type: 'RADIO_BUTTON', }, '小計': { value: '10000', type: 'NUMBER', }, }, { $id: { value: 2, type: 'RECORD_NUMBER', }, '税種別': { value: '税抜', type: 'RADIO_BUTTON', }, '小計': { value: '20000', type: 'NUMBER', }, }, { $id: { value: 3, type: 'RECORD_NUMBER', }, '税種別': { value: '税込', type: 'RADIO_BUTTON', }, '小計': { value: '30000', type: 'NUMBER', }, }, ]; // 税種別フィールドに[税込]か[税抜]が入り // 小計フィールドに金額が入るものとします。 // Arrow関数の場合 const includedTaxPriceArrayWithArrowFunc = records.filter(record => record['税種別'].value === '税込').map(record => record['小計'].value * 1.1); console.log(includedTaxPriceArrayWithArrowFunc);
return
がいらない分、関数を作る関数(カリー化)を作りやすくなります。
...
spread opereatorなどは後述で説明します。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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
const contracts = [ { '契約名': { 'type': 'SINGLE_LINE_TEXT', 'value': '契約C' }, '料金': { 'type': 'NUMBER', 'value': '50000' }, '商品ID': { 'type': 'NUMBER', 'value': '1' }, '顧客ID': { 'type': 'NUMBER', 'value': '3' }, '日付': { 'type': 'DATE', 'value': '2019-08-31' }, '$id': { 'type': '__ID__', 'value': '3' } }, { '契約名': { 'type': 'SINGLE_LINE_TEXT', 'value': '契約B' }, '料金': { 'type': 'NUMBER', 'value': '150000' }, '商品ID': { 'type': 'NUMBER', 'value': '2' }, '顧客ID': { 'type': 'NUMBER', 'value': '4' }, '日付': { 'type': 'DATE', 'value': '2019-08-30' }, '$id': { 'type': '__ID__', 'value': '2' } }, { 'レコード番号': { 'type': 'RECORD_NUMBER', 'value': '1' }, '契約名': { 'type': 'SINGLE_LINE_TEXT', 'value': '契約A' }, '料金': { 'type': 'NUMBER', 'value': '100000' }, '商品ID': { 'type': 'NUMBER', 'value': '3' }, '顧客ID': { 'type': 'NUMBER', 'value': '1' }, '日付': { 'type': 'DATE', 'value': '2019-08-29' }, '$id': { 'type': '__ID__', 'value': '1' } } ]; const customers = [ { '顧客名': { 'type': 'SINGLE_LINE_TEXT', 'value': 'サンプルカンパニー' }, '$id': { 'type': '__ID__', 'value': '5' } }, { '顧客名': { 'type': 'SINGLE_LINE_TEXT', 'value': 'まぐろ水産' }, '$id': { 'type': '__ID__', 'value': '4' } }, { '顧客名': { 'type': 'SINGLE_LINE_TEXT', 'value': 'サンプル株式会社' }, '$id': { 'type': '__ID__', 'value': '3' } }, { '顧客名': { 'type': 'SINGLE_LINE_TEXT', 'value': '町田商事' }, '$id': { 'type': '__ID__', 'value': '2' } }, { '顧客名': { 'type': 'SINGLE_LINE_TEXT', 'value': '金都運総研' }, '$id': { 'type': '__ID__', 'value': '1' } } ]; const products = [ { '商品名': { 'type': 'SINGLE_LINE_TEXT', 'value': 'パソコン' }, '$id': { 'type': '__ID__', 'value': '3' } }, { '商品名': { 'type': 'SINGLE_LINE_TEXT', 'value': '業務用冷蔵庫' }, '$id': { 'type': '__ID__', 'value': '2' } }, { '商品名': { 'type': 'SINGLE_LINE_TEXT', 'value': '大型テレビ' }, '$id': { 'type': '__ID__', 'value': '1' } } ]; // それぞれ、契約アプリのレコード、それに紐付いた顧客アプリのレコード・商品アプリのレコードがあるとします。 // ある複数のレコードを結合させるための関数を返す関数を作成 const joinRecordsCreator = (leftRecords) => (rightRecords, joinKey) => leftRecords.map(leftRecord => ({...rightRecords.find((rightRecord) => rightRecord[joinKey].value === leftRecord.$id.value), ...leftRecord})) // 上記からContractsを他のアプリのレコードに結合させる関数を作成 const joinContracts = joinRecordsCreator(contracts); // joinContracts関数を使ってContractsとCustomersを結合した配列を取得 const contractsJoinedWithCustomers = joinContracts(customers, '$id'); // joinContracts関数を使ってContractsとProductsを結合した配列を取得 const contractsJoinedWithProducts = joinContracts(products, '$id'); console.log(contractsJoinedWithCustomers); console.log(contractsJoinedWithProducts);
-
this
を固定できる。kintoneのJSカスタマイズではDOMを操作するときに
this
を参照するケースはあると思います。
通常の関数宣言の場合「this
は何を指しているか?」を意識する必要がありましたが、arrow関数の場合this
が固定されるのでその混乱はなくなります。
その代わりcurrentTarget
などをつかって押された要素自体を取得するなどができます。1 2 3 4 5 6 7 8 9 10
// 通常の関数の場合、thisはボタン自身になる $('#button1').click(function() { console.dir(this); // button要素を示す。 }); // アロー関数の場合、thisは固定されているためbutton要素ではない。 // その代わり、コールバックの引数のcurrentTargetを使う $('#button2').click((e) => { console.dir(e.currentTarget); // button要素を示す。 });
Promiseの代わりにAsync/Awaitを使う
複数のアプリのレコードからデータを取得したい場合などに、Promiseによる非同期処理をしなければいけませんが、
Promiseは.then()
に次の処理を書く都合上、入れ子構造になり慣れていないとあまり直感的とはいえません。
下記あるアプリのレコードを4件取得して合計値を求めるサンプルで試したいと思います。
-
例)4件のデータを取得し合計値を求める。(Promise)
レコード一括取得APIを使うほうが適切ですがPromiseのサンプルのためにあえて1件ずつ取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
(function() { 'use strict'; // 4件のデータを取得し合計値を求めることを想定 // params1-4は省略します。 // kintoneはPromiseを返すことで保存前処理など、処理をまってくれる kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], function(event) { return new kintone.Promise(function(resolve, reject) { return kintone.api('/k/v1/record', 'GET', params1); }).then(function(resp1) { // レスポンス内容がresp1に入る。ちゃんと待ってから次の処理に移る return kintone.api('/k/v1/record', 'GET', params2); }).then(function(resp2) { // レスポンス内容がresp2に入る。ちゃんと待ってから次の処理に移る return kintone.api('/k/v1/record', 'GET', params3); }).then(function(resp3) { // レスポンス内容がresp3に入る。ちゃんと待ってから次の処理に移る return kintone.api('/k/v1/record', 'GET', params4); }).then(function(resp4) { // レスポンス内容がresp4に入る。ちゃんと待ってから次の処理に移る // ここにくるまでに全てのデータの取得を完了しているので計算できる // resp1 - 4までの合計をたして「合計フィールド」にセット event.record.合計.value = resp1.record.小計.value + resp2.record.小計.value + resp3.record.小計.value + resp4.record.小計.value; resolve(event); // resolveでプロミスの処理が終了したことを伝える }); }); })();
そこで、Promiseをそのままつかうのではなく、Async/Awaitを用いることで次のように普通の関数を使うようにかけるようになりました。
どちらが正しい、とかではないのですが、複数のPromiseだったり複雑だったりするとAsync/Awaitの書き方のほうが見通しがよいかと思います。 -
例)4件のデータを取得し合計値を求める(Async/Await)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
(() => { 'use strict'; // 4件のデータを取得し合計値を求めることを想定 // params1-4は省略します。 kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], async function(event) { // asyncをつける // 待ちたい処理にawaitをつける const resp1 = await kintone.api('/k/v1/record', 'GET', params1); // レスポンス内容がresp1に入る。ちゃんと待ってから次の処理に移る const resp2 = await kintone.api('/k/v1/record', 'GET', params2); // レスポンス内容がresp2に入る。ちゃんと待ってから次の処理に移る const resp3 = await kintone.api('/k/v1/record', 'GET', params3); // レスポンス内容がresp3に入る。ちゃんと待ってから次の処理に移る const resp4 = await kintone.api('/k/v1/record', 'GET', params4); // レスポンス内容がresp4に入る。ちゃんと待ってから次の処理に移る // ここにくるまでに全てのデータの取得を完了しているので計算できる // resp1 - 4までの合計をたして「合計フィールド」にセット event.record.合計.value = resp1.record.小計.value + resp2.record.小計.value + resp3.record.小計.value + resp4.record.小計.value; return event; }); })();
ただし、注意点としては、
async
をつけることでPromiseObject
が返却されてしまうので、Promiseに対応していないイベントハンドラーでは即時実行関数で包むなどしてあげないとエラーが発生してしまいます。
分割代入でスマートに変数を取り出す
これも便利な機能です。
通常、次のようにkintone.events.on()
の中でrecord
変数を取り出すときはこうします。
-
kintone.events.on()
の中でevent.record
からrecord
変数を取り出す。(通常)1 2 3 4
kintone.events.on('app.record.create.submit', (event) => { const record = event.record; console.log(record); });
分割代入を使うと次のように2回
record
と書かなくて良くなります。 -
kintone.events.on()
の中でevent.record
からrecord
変数を取り出す。(分割代入)1 2 3 4
kintone.events.on('app.record.create.submit', (event) => { const {record} = event; console.log(record); });
このように、わざわざ
record
と2回書く手間が省けます。
さらに潜ってrecord
のprice
とcustomer
フィールドを取りたい場合はこのようにします。1 2 3 4 5
kintone.events.on('app.record.create.submit', (event) => { const {record} = event; const {price, customer} = record; console.log(price, customer); });
先に
price
とcustomer
をとってしまうことも可能です。1 2 3 4
kintone.events.on('app.record.create.submit', (event) => { const {record: {price, customer}} = event; console.log(price, customer); });
必須な書き方ではありませんが値取り出すために数行かかってしまうと見通しが悪くなってしまうこともあるので、スマートに取りたい場合は有効です。
スプレッド構文を使って展開する
スプレッド構文も使いこなせば便利です。
スプレッド構文は3つのピリオド...
を使い、配列・関数の引数・オブジェクトを展開できます。
配列で使うスプレッド構文
-
kintoneのレコードの配列
records
配列を展開し結合します。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
const records1 = [ { '契約名': { 'type': 'SINGLE_LINE_TEXT', 'value': '契約C' }, '料金': { 'type': 'NUMBER', 'value': '50000' }, '日付': { 'type': 'DATE', 'value': '2019-08-31' }, '$id': { 'type': '__ID__', 'value': '3' } }, { '契約名': { 'type': 'SINGLE_LINE_TEXT', 'value': '契約B' }, '料金': { 'type': 'NUMBER', 'value': '150000' }, '日付': { 'type': 'DATE', 'value': '2019-08-30' }, '$id': { 'type': '__ID__', 'value': '2' } }, ]; const records2 = [ { '契約名': { 'type': 'SINGLE_LINE_TEXT', 'value': '契約A' }, '料金': { 'type': 'NUMBER', 'value': '100000' }, '日付': { 'type': 'DATE', 'value': '2019-08-29' }, '$id': { 'type': '__ID__', 'value': '1' } } ]; // レコード配列records1, records2を結合して一つの配列にする const resultRecords = [...records1, ...records2]; console.log(resultRecords);
結合は
Array.prototype.concat()
でもできますが、こちらのほうが直感的でしょう。
records1
、records2
でそれぞれ3つ要素があるとすれば、[...records1, ...records2]
とすることで、次の記載と同義になります。1
[records1-1, records1-2, records1-3, records2-1, records2-2, records2-3]
...
で配列を一度展開していると思えばわかりやすいかと思います。
関数の引数で使うスプレッド構文
関数の引数に使えば可変数の引数に対応できます。
-
複数の契約レコードの合計金額を集計する関数
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
const record1 = { '契約名': { 'type': 'SINGLE_LINE_TEXT', 'value': '契約C' }, '料金': { 'type': 'NUMBER', 'value': '50000' }, '日付': { 'type': 'DATE', 'value': '2019-08-31' }, '$id': { 'type': '__ID__', 'value': '3' } }; const record2 = { '契約名': { 'type': 'SINGLE_LINE_TEXT', 'value': '契約B' }, '料金': { 'type': 'NUMBER', 'value': '150000' }, '日付': { 'type': 'DATE', 'value': '2019-08-30' }, '$id': { 'type': '__ID__', 'value': '2' } }; const record3 = { '契約名': { 'type': 'SINGLE_LINE_TEXT', 'value': '契約A' }, '料金': { 'type': 'NUMBER', 'value': '100000' }, '日付': { 'type': 'DATE', 'value': '2019-08-29' }, '$id': { 'type': '__ID__', 'value': '1' } }; const sumContractPriceRecords = (...records) => { // 引数recordsは配列となる return records.reduce((sum, record) => sum + Number(record.料金.value), 0); }; // record1,2,3のそれぞれの小計の合計をだす const sum = sumContractPriceRecords(record1, record2, record3); console.log(sum);
オブジェクトで使うスプレッド構文
オブジェクトも配列のように展開がされますので、次の用にオブジェクト同士の結合ができます。
|
|
これも、配列と同様にcontract
レコードとcustomer
レコードが一度展開されているイメージです。
副作用をなくして安全にコードを書く
プログラミングで、状態を変化させることを副作用と呼びますが、副作用を起こすコードはなるべく少ないほうがバグを回避できます。
言葉だけではわかりにくいので、起こりうるバグを見てみましょう。
|
|
直感的には、上記のコードはコンソールに、税込金額(1100)と税抜金額(1000)を表示してくれそうです。
しかし実際には次のように表示されます。
なんと、両方とも税込金額(1100)になってしまっていますね。
これは「もとの変数に対して副作用が起きている」状態となります。
このように、副作用がいつ起こるかというのを意識していなければ、思わぬバグを生んでしまいます。
JavaScriptでは常に変数は値への参照なので起きてしまう現象です。
これを回避するために、SpreadOperatorで次のようにaddTax関数を書き換えましょう。
新しいObjectを作り、返すようなイメージです。
|
|
jQueryを使わず素のJSでDOMを操作する
もちろん込み入ったDOM操作をしたい場合jQueryは有用です。
ですが1つのDOMを操作したい場合に、わざわざjQueryを読み込むのは冗長なこともあります。
素のJSでもDOMを操作しやすくなっているので紹介します。
-
要素を取得する。
document.querySelector()
/querySelectorAll()
を利用することで要素を取得できます。HTMLは次のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<div id="foo"> foo </div> <div id="bar"> bar </div> <div class="foo"> foo class1 </div> <div class="foo"> foo class2 </div> <div class="foo"> foo class3 </div>
JavaScriptは次のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/* 要素id = fooの要素を取得する場合 */ // jQuery const elementJquery = $('#foo'); // JavaScript const element = document.querySelector('#foo'); // Classなど複数の要素を取得したい場合はquerySelectorAll const elements = document.querySelectorAll('.foo'); console.log('取得した要素のtext: ', elementJquery.text()); console.log('取得した要素のtext: ', element.innerText); console.log('取得した要素のtext: ', [...elements].map(e => e.innerText));
document.getElementById()
などもありますが、上記のようにclass/idに縛られないquerySelector
のほうが柔軟に要素を指定できます。 -
取得した要素のStyleを変更する。
要素.style.プロパティ = "値";
とすることで、要素に対してstyleを変更できます。
CSSで設定できるstyleを割り当てられます。1 2 3 4
// id: fooの要素の文字を、赤い太文字にする const element = document.querySelector('#foo'); element.style.color = `red`; element.style.fontWeight = `bold`;
もちろん
kintone.app.record.getFieldElement
などで取得した要素にも同様のことが可能です。
TypeScriptを導入する
TypeScriptでkintoneカスタマイズ開発をしてみよう
の記事で紹介されています。
kintoneのサブテーブルのRecord構造などは結構難しいので、TypeScriptを使うと、オブジェクトのキーの指定や型の間違いなどをなくすことができます。
Visual Studio CodeなどのIDEを使えば補完しながらコードを書くこともできます。
がっつりJavaScriptカスタマイズをしたい場合は導入したほうがよいです。
おわりに
JavaScriptはよりプログラミングしやすくなるように進化していっています。
const
やlet
、Spread構文やArrow関数などを組み合わせたり新しい機能を使うと、バグを減らし副作用のない堅牢なコードも書けるようになると思います。
少しずつでもぜひ試してみてください。