OnVisibleの「入口」は整理できた。では「中身」は?
前回の記事では、Navigate関数の第3引数に { action: "..." } という形で遷移の意図を渡す Navigation Context パターン を整理・検討しました。
Switch文でactionを受け取り、OnVisibleの処理を一元的に振り分けるこの構造は、フラグの立て下ろし管理を不要にし、遷移パターンが増えてもSwitch文にcaseを追加するだけで対応できるというものでした。
しかし、アプリがさらに育っていくと、今度はSwitch文の中身に問題が生まれてきました。
この記事で伝えたいこと
- Switch直書きで起きる「処理ブロックの重複」という課題
- 「何をするかの宣言」と「実行」を分ける Execution Plan パターンによる試行錯誤
前提
- Power Apps のCanvasアプリ
- 前回と同じ商品管理アプリ(InitScreen / MainScreen / FilterScreen / DetailScreen)の構成
- Navigation Contextパターンを導入済みで、MainScreen.OnVisibleはSwitch文でactionを受け取っている
- AIと協働しながら、より良い設計パターンを試行錯誤中
遷移パターンが増えてくると
前回の時点では、MainScreenへの遷移は3パターンでした。
| action | 意味 |
|---|---|
init |
アプリ起動時の初期化 |
applyFilter |
絞り込み条件を適用して戻る |
back |
詳細画面から戻る(何もしない) |
開発を続けていって、新たに2つの遷移パターンが追加になったとします。
| action | 意味 |
|---|---|
resetFilter |
フィルターリセット後に商品一覧を再取得(新追加) |
editComplete |
商品の編集完了後に戻る。商品一覧を再取得(新追加) |
5つのパターンに対応するため、Switch文を素直に拡張するとどうなるでしょうか。
Switch直書きで起きること
MainScreen.OnVisible(Switch直書き)
Switch(
locNavigationContext.action,
"init",
ClearCollect(colCategories, Categories);
ClearCollect(colProducts, Products),
"applyFilter",
ClearCollect(
colProducts,
Filter(
Products,
(IsBlank(gblFilterCategory) || Category = gblFilterCategory)
&& (IsBlank(gblFilterStatus) || Status = gblFilterStatus)
&& Price >= gblFilterPriceMin
&& Price <= gblFilterPriceMax
)
),
"back",
// 何もしない
false,
// ❌ 新追加:フィルターリセット後、商品一覧を再取得
"resetFilter",
ClearCollect(colProducts, Products), // ← initと同じ処理が再登場
// ❌ 新追加:商品編集完了後、一覧を再取得
"editComplete",
ClearCollect(colProducts, Products) // ← またinitと同じ処理が再登場
)
何が起きているか
ClearCollect(colProducts, Products) が init / resetFilter / editComplete の3ヶ所に登場しています。これでもアプリは動きますが、次のような問題が生まれています。
- 処理ロジックの修正コストが増える:商品の取得条件に変更が入ったとき、重複している全ての条件分岐を漏れなく修正しなければなりません。
- 「このactionで何が実行されるか」が追いにくい:各条件分岐を全て読まないと、何が実行されるか把握できません。
- 新しいactionを追加するときの判断が難しい:「どの処理ブロックをコピペすればいいか」を毎回考え直す必要があります。
Navigation Contextパターンで「OnVisibleの入口」は整理できましたが、今度は「入口の後の中身」に同様の問題が起きています。
解決策を考えてみる:「何をするか」と「どう実行するか」を分ける
Switch直書きで重複が起きる根本的な原因は、「どのactionか」の判別と**「処理の実行」**が一つのSwitch文に混在していることです。
各条件分岐が「この処理をここで実行する」という責任を持つ構造のため、同じ処理が複数の条件分岐に書かれることになります。
AIとの試行錯誤の中でたどり着いたのは、この2つを分離するというアプローチです。
- Phase 1(What):Switchでactionを判別し、「どの処理ブロックを実行するか」をプランとして宣言する
- Phase 2(How):プランに従って、各処理ブロックをそれぞれ一度だけ実行する
このアプローチを、Execution Plan パターン とこの記事では呼ぶことにします。
Execution Plan パターン
MainScreen.OnVisible
// =============================================
// Phase 1: 実行プランの宣言
// (何をするかをactionに応じて決める)
// =============================================
Switch(
locNavigationContext.action,
/*
"actionName",
UpdateContext({ locPlan: {
// カテゴリの一覧を取得するかどうか
shouldLoadCategories: true,
// 商品一覧を取得するかどうか
shouldLoadProducts: true,
// 商品一覧のフィルタ処理をするかどうか
shouldApplyFilter: true
}}),
*/
"init",
UpdateContext({ locPlan: {
shouldLoadCategories: true,
shouldLoadProducts: true,
shouldApplyFilter: false
}}),
"applyFilter",
UpdateContext({ locPlan: {
shouldLoadCategories: false,
shouldLoadProducts: false,
shouldApplyFilter: true
}}),
"back",
UpdateContext({ locPlan: {
shouldLoadCategories: false,
shouldLoadProducts: false,
shouldApplyFilter: false
}}),
"resetFilter",
UpdateContext({ locPlan: {
shouldLoadCategories: false,
shouldLoadProducts: true,
shouldApplyFilter: false
}}),
"editComplete",
UpdateContext({ locPlan: {
shouldLoadCategories: false,
shouldLoadProducts: true,
shouldApplyFilter: false
}})
);
// =============================================
// Phase 2: プランに従って実行
// (各処理ブロックはここに一度だけ書く)
// =============================================
If(locPlan.shouldLoadCategories,
ClearCollect(colCategories, Categories)
);
If(locPlan.shouldLoadProducts,
ClearCollect(colProducts, Products)
);
If(locPlan.shouldApplyFilter,
ClearCollect(
colProducts,
Filter(
Products,
(IsBlank(gblFilterCategory) || Category = gblFilterCategory)
&& (IsBlank(gblFilterStatus) || Status = gblFilterStatus)
&& Price >= gblFilterPriceMin
&& Price <= gblFilterPriceMax
)
)
);
Switch直書きとの比較
| Switch直書き | Execution Plan | |
|---|---|---|
| 処理ブロックの記述 | 各条件分岐に重複して書く | 各ブロックを一度だけ書く |
| 「このactionで何が実行されるか」 | 各条件分岐を全て読む必要あり | Phase 1のプランを見ればわかる |
| 処理ロジックの修正 | 重複する全条件分岐を修正 | Phase 2の1箇所だけ修正 |
| 新しいactionの追加 | どの処理をコピペするか判断 | プランにフラグをセットするだけ |
| 実行順序 | 条件分岐ごとに暗黙的 | Phase 2で明示的に定義 |
新しいactionを追加したいときは、Phase 1のSwitch文にcaseを一つ追加し、フラグをセットするだけです。Phase 2の処理ブロックには一切触れる必要がありません。
ポイント
「何をするか」と「どう実行するか」の分離
ソフトウェア設計には、「宣言的プログラミング(Declarative Programming)」という考え方があります。「どう処理するか(How)」を手続きとして記述するのではなく、「何をしたいか(What)」を宣言し、その実行は別の仕組みに任せるというアプローチです。
Execution PlanパターンのPhase 1は、まさにこの「What」の宣言にあたります。「このactionでは、shouldLoadCategories = true、shouldLoadProducts = true」という形で、実行すべき処理を宣言的に表現しています。Phase 2がその宣言に従って実行する役割を担います。
この分離によって、「このactionで何が起きるか」はPhase 1を読めばわかり、「各処理ブロックがどう動くか」はPhase 2を読めばわかる、という責任の明確化が生まれます。
プランを「レコード型」で持つことのメリット
locPlan をレコード型({ shouldLoadProducts: true, ... })で持っていることにも意図があります。
仮にフラグをバラバラのコンテキスト変数で持った場合、Phase 1のSwitch文では「このactionに関係するフラグだけ」を書くことになりがちです。すると、前のactionのフラグが意図せず残ってしまうリスクが生まれます。
レコード型でまとめることで、actionが切り替わるたびに全フラグが必ず上書きされます。これにより「前のactionの残留フラグ」という問題が構造的に起きなくなります。
まとめ
Navigation Contextパターンで整理した「OnVisibleの入口」に続き、今回は入口の後の処理ブロックをどう整理するかについて考えました。
Execution Planパターンのポイントは次の3つです。
- 処理ブロックを一度だけ書く:重複がなくなり、修正が一箇所に集約される
- 「何をするかの宣言」と「実行」を分ける:Phase 1を読めば各actionの振る舞いが一覧でき、Phase 2を読めば処理の詳細がわかる
- 新しいactionの追加が既存コードに影響しない:Phase 1にcaseを追加するだけで、Phase 2には触れない
この3つのポイントにより、結果的にOnVisibleで何が何のために行われているか?がより明示的になり、コードを改修する際にも「余計な部分に意図せず手を入れてしまうリスク」を減少できたかなと思います。
3回にわたって、「とりあえず動く」コードから一歩ずつ整理していく過程を追いかけてきました。
「とりあえず動く」→「安心して変更できる」→「意図が読めるから安心して任せられる」→「構造が見えるから全体を把握できる」
処理の書き方一つで、アプリの育てやすさは大きく変わってきます。自分なりに試行錯誤しているこれらの設計パターンが、Power Appsアプリの設計を考えるときの一つの視点として役に立てば嬉しいです。

