Power Apps でアプリ開発をしていて、「同じIf文をあちこちにコピペして修正が大変」「数式が長すぎて解読不能」という話をよく耳にする。
待望の機能「ユーザー定義関数(UDF)」を使えば、そのロジックを1箇所にまとめて再利用できるようになる!
この記事では、App.Formulasを使った関数の定義方法から、実務ですぐ使える「共通デザイン定義」「入力値チェック」「状態管理(Reducerパターン)」の3つの活用パターンを、コード付きで解説。
もうスパゲッティコードとはおさらばして、メンテナンスしやすいアプリを作ろう!
ユーザー定義関数(UDF)
ユーザー定義関数は、「引数と戻り値を指定する関数」を事前に定義できる、関数宣言のような機能。
他の言語(JavaScript, Python, C#)だと普通に用意されているんだけど、Power Appsには用意されていなくて、ずっと欲しかった機能の一つ。
これでPower Appsの「ロジックの再利用」や「コードのメンテナンス性向上」に劇的な効果がでる!
実際に使ってみた方が早いと思うので、実例を交えてご紹介。
基本的な使い方
※App.Formulas:「Appオブジェクトを選択 > プロパティプルダウンから Formulas を選択」
// 定義方法 関数名(引数1:引数1の型, 引数2:引数2の型...):戻り値の型 = 【定義する式】; // 例:週末を求める関数 IsWeekEnd(入力:日付, 戻り値:bool) IsWeekEnd(d:Date):Boolean = Weekday(d) in [1,7];




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



そんなときにユーザー定義関数を使って、「入力値チェックの式」を受け「文字色(通常 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の型...):戻り値の型 = {【定義する式】};
この改修で「アプリの状態の一元管理」も可能に。
【上級編】Reducerパターンで複雑な状態管理を攻略する(Reduxライクな実装)





カートの状態を更新する際に必ずこの関数を呼び出すようにすると、カートに関連するバグが発生したときにこの関数から調査を開始すればよいので、デバッグの時間がかなり削減できる。
とは言え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);
)
};
ということで、ユーザー定義関数は非常に便利なので、今後使う機会はかなり増えそう。

