
テーブル駆動メソッドとステートパターンでフロントエンド状態を管理する
この記事はAIによって翻訳されたため、不正確な場合があります。
免責聲明: このブログ記事の翻訳はAIによって生成されました。翻訳の正確性について保証はありません。重要な情報については、元の記事をご参照ください。
カバー写真は @shawnanggg による Unsplash より
この記事の目的は、コンポーネント内の状態分岐ロジックを管理するパターンを共有することです。
フロントエンド開発では、異なる状態に基づいて異なるUIや動作を切り替えることが一般的なケースです。例えば:
- キャンペーンページがキャンペーン状態、ログイン状態などに基づいて異なるUIを表示する
- 商品コンポーネントが商品状態、レビュー状態、違反状態などによって異なる表示をする
これを注意深く処理しないと、コード内で複雑なロジックになってしまいます。保守が困難でエラーが発生しやすくなります。
これを緩和するために、以下の2つのアプローチを利用できます:
- テーブル駆動メソッド%20to%20figure%20it%20out.)
- ステートパターン
テーブル駆動メソッド
これはCode Completeという本からのプログラミング手法で、if else分岐ロジックをより簡潔な方法で管理します(場合によっては)。
例を見てみましょう:
// If Else
if (a === 'foo') {
if (b === 'abc') {
action1()
} else if (b === 'xyz') {
action2()
}
} else if (a === 'bar') {
action3()
}
// テーブル駆動
const action = {
foo: {
abc: action1,
xyz: action2,
},
bar: {
abc: action3,
xyz: action3,
},
}[a][b]
action()
2番目のアプローチには重複するaction3
宣言がありますが、この場合はより直接的です。ただし、多くの状態に対してわずかなアクションしかない場合、テーブル駆動アプローチはかなり冗長で詳細になる可能性があることを覚えておいてください。どちらをいつ使うかのバランスを見つけるのは私たち次第です。
このようなテーブル駆動JSコードを作成するシンプルなツールがあります。ぜひご覧ください。このツールについてご意見をお聞かせください :)
https://playground.kenneth.pro/easy-state
ステートパターン
私の理解では、GoFステートパターンの主なアイデアは:
与えられた状態によって予め定義されたメソッドと変数のセットを最初に選択し、同じインターフェースを提供することで、その依存関係が詳細を知ることなくインターフェースを使用できるようにする。
ステートパターンを利用する場合、実装は以下のようになります:
- 切り替える必要がある変数とメソッドをすべて最初に事前定義し、与えられた状態によってセットを選択する。
TypeScriptでの実際の例を見てみましょう。先ほど話したテーブル駆動メソッドと組み合わせています。
この例では、CampaignComponent
が異なるLoginStatus
、CampaignStatus
、UserCampaignStatus
に基づいて異なるUIを表示します:
// ステータスタイプを定義
enum LoginStatus {
NOT_LOGIN,
LOGIN,
}
enum CampaignStatus {
NOT_STARTED,
STARTED,
ENDED,
}
enum UserCampaignStatus {
NOT_ATTENDED,
ATTENDED,
}
// コンポーネントプロップタイプを定義
interface Props {
banner: string
submit: () => void
// ...
}
// 具体的なプロップを定義
const NotStarted: Props = { banner: 'Campaign Not stated' /*...*/ }
const NotLogin: Props = { banner: 'Please login' /*...*/ }
const Eligible: Props = { banner: 'Welcome!!' /*...*/ }
const Attended: Props = { banner: 'Attended before' /*...*/ }
const Ended: Props = { banner: 'Campaign is ended' /*...*/ }
// 状態マッピングを定義
const States = {
[LoginStatus.NOT_LOGIN]: {
[CampaignStatus.NOT_STARTED]: {
[UserCampaignStatus.NOT_ATTENDED]: NotStarted,
[UserCampaignStatus.ATTENDED]: NotStarted,
},
[CampaignStatus.STARTED]: {
[UserCampaignStatus.NOT_ATTENDED]: NotLogin,
[UserCampaignStatus.ATTENDED]: NotLogin,
},
[CampaignStatus.ENDED]: {
[UserCampaignStatus.NOT_ATTENDED]: CampaignEnded,
[UserCampaignStatus.ATTENDED]: CampaignEnded,
},
},
[LoginStatus.LOGIN]: {
[CampaignStatus.NOT_STARTED]: {
[UserCampaignStatus.NOT_ATTENDED]: NotStarted,
[UserCampaignStatus.ATTENDED]: NotStarted,
},
[CampaignStatus.STARTED]: {
[UserCampaignStatus.NOT_ATTENDED]: Eligible,
[UserCampaignStatus.ATTENDED]: Attended,
},
[CampaignStatus.ENDED]: {
[UserCampaignStatus.NOT_ATTENDED]: CampaignEnded,
[UserCampaignStatus.ATTENDED]: CampaignEnded,
},
},
}
この例では、状態分岐の一部が冗長で詳細ですが、ビジネスロジックをUIコンポーネントから分離しています。何らかの形でより テスト可能で保守可能です。
Reactを例にとると、カスタムフックやReduxコンテナでプロップを準備できます。
さらに、States
オブジェクトを関数でラップして、特定のビジネスニーズに対してより直接的または冗長でなくすることができます:
const getState = (
loginStatus: LoginStatus,
campaignStatus: CampaignStatus,
userCampaignStatus: UserCampaignStatus
) => {
if (campaignStatus === CampaignStatus.NOT_STARTED) return NotStarted
if (campaignStatus === CampaignStatus.ENDED) return CampaignEnded
const States = {
[LoginStatus.NOT_LOGIN]: {
[CampaignStatus.STARTED]: {
[UserCampaignStatus.NOT_ATTENDED]: NotLogin,
[UserCampaignStatus.ATTENDED]: NotLogin,
},
},
[LoginStatus.LOGIN]: {
[CampaignStatus.STARTED]: {
[UserCampaignStatus.NOT_ATTENDED]: Eligible,
[UserCampaignStatus.ATTENDED]: Attended,
},
},
}
return States[loginStatus][campaignStatus][userCampaignStatus]
}
コードが理解可能で、テスト可能で、一貫性があり、正しく動作する限り、どのパラダイムを使用しているかは問題ありません。誰もが一緒に作業できる良いコードです。
この投稿が多少なりともお役に立てることを願っています。ハッピー開発!