個人的に、ずっと切望していた機能「ユーザー定義関数」が(実験段階だけど)公開されたので、使い方を詳しく調べてみた。
※本記事の内容は「実験段階」の機能です。今後変更される可能性もあります。
ユーザー定義関数(UDF)
ユーザー定義関数は、「引数と戻り値を指定する関数」を事前に定義できる、関数宣言のような機能。
他の言語(JavaScript, Python, C#)だと普通に用意されているんだけど、Power Appsには用意されていなくて、ずっと欲しかった機能の一つ。
実際に使ってみた方が早いと思うので、実例を交えてご紹介。
事前設定
ユーザー定義関数を使用するには設定の変更が必要。[設定]→[更新]→[試験段階]→[ユーザー定義関数]をONにする。
※設定を変えた後、保存してPower Apps Studioを再起動する必要あり。

※設定を変えた後、保存してPower Apps Studioを再起動する必要あり。

基本的な使い方
ユーザー定義関数は、App.Formulas内で定義することができる。
// 定義方法 関数名(引数1:引数1の型, 引数2:引数2の型...):戻り値の型 = 【定義する式】; // 例:週末を求める関数 IsWeekEnd(入力:日付, 戻り値:bool) IsWeekEnd(d:Date):Boolean = Weekday(d) in [1,7];
例のように「指定の日付が週末か」を判定する関数を用意しておけば、日付の色の変更にも使えるし、


休日のときだけアイコンを出す、といった処理にも、


ボタン押下時の判定にも使える。


今までは各コントロールでこの判定を行う必要があったので、かなり構築が楽になる。
以下、さらに使用例を紹介。
使用例1:アプリ共通のデザインの定義
ユーザー定義関数は、アプリ共通のデザインを当てる際にも使うことができる。
例えば「郵便番号」や「携帯番号」などの入力を求めるアプリを作るときに、もしこれらの値が指定のフォーマットに沿っていなかったら文字色を「アプリ独自のエラー色(例だと赤)」にしたいときを考える。
今までだとFormulasにこんな感じで色を定義して、


各テキスト入力にIf文で色を定義していた。




この方法でも「文字色の変更(仕様変更)」には対応できるからいいんだけど、各所にIf文をコピペしていくのが面倒。。
そんなときにユーザー定義関数を使って、「入力値チェックの式」を受け「文字色(通常 or エラー)」を返す関数を定義すると、
そんなときにユーザー定義関数を使って、「入力値チェックの式」を受け「文字色(通常 or エラー)」を返す関数を定義すると、
ValidatedTextInputColor(validation:Boolean):Color = If(validation, TextColor.Normal, TextColor.Error);
各コントロールではこの関数を、引数に「チェック式」を入れて呼び出せばよいので、だいぶ簡潔になる。
// 郵便番号チェック ValidatedTextInputColor(IsMatch(Self.Value, "^\d{3}-?\d{4}$")) // 携帯電話チェック ValidatedTextInputColor(IsMatch(Self.Value, "^0\d{2}-?\d{4}-?\d{4}$"))
使用例2:入力チェック
さらに、この「入力値をチェックする機能」自体も、関数として定義することができる。
// 郵便番号、携帯番号、メールの書式チェック // ※この正規表現はあくまで例として作成したものです。動作を保証するものではありません。 IsValidFormattedText(type:Text, inputText:Text):Boolean = Switch(type, "PostalCode", IsMatch(inputText, "^\d{3}-?\d{4}$"), "Mobile", IsMatch(inputText, "^0\d{2}-?\d{4}-?\d{4}$"), "Mail", IsMatch(inputText, "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"), false );
このようにチェック式自体を定義しておくことで、各コントロールではこのように「書式」と「チェックする対象」を指定するだけで色が指定できるし、


もちろん入力値チェックにも流用できる。


このように「色々なアプリで使える関数」を誰かが作成して、みんなに共有することもできるので便利。
SetやCollectなどの動作関数も使える
さらに最近SetやCollectなどの動作関数も、{}で式を囲むことで宣言できるようになった。
関数名(引数1:引数1の型, 引数2:引数2の型...):戻り値の型 = {【定義する式】};
この改修で「アプリの状態の一元管理」も可能に。
使用例3:アプリの状態一元管理
例えばショッピングカートのように「アプリ全体で管理すべき状態(カートの中身)」があるアプリを作りたい場合、このようにReducer風の関数を定義すれば、アプリの状態の変更ロジックを全てFormulas内に集約できる。


こうして定義しておけば、「カートに追加」ボタンの処理はこのようになるし、


「数量の追加」アイコン、


「数量を減らす」アイコン、


「商品削除」アイコン、といった感じになる。


カートの状態を更新する際に必ずこの関数を呼び出すようにすると、カートに関連するバグが発生したときにこの関数から調査を開始すればよいので、デバッグの時間がかなり削減できる。
とは言えPower Appsでここまで管理すべきかは怪しいので、グローバル変数(サイドバーの開閉状態とか)を変更する際に、必ずこの定義関数を経由するようにするだけでも、かなりアプリの状態の管理はしやすくなるかと。
Product := Type({id:Text, name:Text, price:Number, quantity:Number}); Action := Type({type:Text, item:Product, quantity:Number}); cartReducer(action:Action):Void={ Switch(action.type, "ADD_ITEM", If(!IsBlank(LookUp(cartItems, id = action.item.id)), // カート内の存在チェック // カートに商品がある場合は数量を+1 UpdateIf(cartItems As i, i.id = action.item.id, {quantity:i.quantity + 1}), // カートに商品がない場合は追加 Collect(cartItems, {id:action.item.id, name:action.item.name, price:action.item.price, quantity:action.quantity}); ), "REMOVE_ITEM", RemoveIf(cartItems, id = action.item.id), "UPDATE_QUANTITY", UpdateIf(cartItems As item, item.id=action.item.id, {quantity:Max(item.quantity + action.quantity, 0)}); // 数量が0の場合は削除 RemoveIf(cartItems, quantity = 0); ) };
ということで、ユーザー定義関数は非常に便利なので、今後使う機会はかなり増えそう。