web-dev-qa-db-ja.com

オブジェクトの配列としての状態とIDをキーとするオブジェクト

状態形状の設計 の章では、ドキュメントはIDをキーとするオブジェクトに状態を保持することを提案しています。

IDをキーとして格納されたオブジェクト内のすべてのエンティティを保持し、IDを使用して他のエンティティまたはリストからそれを参照します。

彼らは状態に進みます

アプリの状態をデータベースと考えてください。

私はフィルターのリストの状態形状に取り組んでいます。フィルターのいくつかは開いている(ポップアップに表示される)か、オプションを選択しています。 「アプリの状態をデータベースとして考える」を読んだとき、APIから返される(それ自体がデータベースに支えられている)JSON応答と考えることを考えました。

だから私はそれを

[{
    id: '1',
    name: 'View',
    open: false,
    options: ['10', '11', '12', '13'],
    selectedOption: ['10'],
    parent: null,
  },
  {
    id: '10',
    name: 'Time & Fees',
    open: false,
    options: ['20', '21', '22', '23', '24'],
    selectedOption: null,
    parent: '1',
  }]

ただし、ドキュメントでは、次のような形式を提案しています。

{
   1: { 
    name: 'View',
    open: false,
    options: ['10', '11', '12', '13'],
    selectedOption: ['10'],
    parent: null,
  },
  10: {
    name: 'Time & Fees',
    open: false,
    options: ['20', '21', '22', '23', '24'],
    selectedOption: null,
    parent: '1',
  }
}

理論的には、 データが(「状態」という見出しの下で)シリアル化可能 であれば問題ではありません。

そこで、減速機を書くまで、オブジェクトの配列アプローチを喜んで使いました。

Object-keyed-by-idアプローチ(およびspread構文の寛大な使用)では、reducerのOPEN_FILTER部分は

switch (action.type) {
  case OPEN_FILTER: {
    return { ...state, { ...state[action.id], open: true } }
  }

一方、オブジェクトの配列アプローチでは、より冗長です(ヘルパー関数に依存)

switch (action.type) {
   case OPEN_FILTER: {
      // relies on getFilterById helper function
      const filter = getFilterById(state, action.id);
      const index = state.indexOf(filter);
      return state
        .slice(0, index)
        .concat([{ ...filter, open: true }])
        .concat(state.slice(index + 1));
    }
    ...

したがって、私の質問は3つあります。

1)レデューサーのシンプルさは、IDによるオブジェクトキーイングアプローチを採用する動機ですか?その状態形状に他の利点はありますか?

そして

2)IDによるオブジェクトキーイングアプローチは、APIの標準JSONの入出力に対処することを難しくしているようです。 (そのため、そもそもオブジェクトの配列を使用しました。)そのアプローチを使用する場合、関数を使用してJSON形式と状態形状形式の間で変換するだけですか?それは不格好なようです。 (もしあなたがそのアプローチを支持するなら、それは上記のオブジェクトの配列リデューサーよりも不格好ではないというあなたの推論の一部ですか?)

そして

3)Dan Abramovが理論的に状態データ構造にとらわれないようにreduxを設計したことを知っています( 「慣例により、トップレベルの状態はオブジェクトまたはMapのような他のキー値コレクションですが、- 技術的には、どのタイプでもかまいません、 " 強調()。しかし、上記のことを考えると、IDでキー設定されたオブジェクトを保持することが「推奨」されているのか、それとも中断するようにオブジェクトの配列を使用して実行する他の予期しない痛みポイントがありますかIDをキーとするオブジェクトを使用して計画を立てますか?

81
nickcoxdotme

Q1:レデューサーのシンプルさは、正しいエントリを見つけるために配列を検索する必要がないためです。配列を検索する必要がないことが利点です。セレクターおよびその他のデータアクセサーは、idによってこれらの項目にアクセスすることがあります。アクセスごとにアレイを検索する必要があると、パフォーマンスの問題になります。アレイが大きくなると、パフォーマンスの問題は急激に悪化します。また、アプリがより複雑になり、より多くの場所でデータを表示およびフィルタリングすると、問題も悪化します。この組み合わせは有害な場合があります。 idで項目にアクセスすると、アクセス時間がO(n)からO(1)に変わります。これは、n(ここでは配列項目)が大きい場合に大きな違いをもたらします。

Q2: normalizr を使用して、APIからストアへの変換を支援できます。 normalizr V3.1.0の時点では、denormalizeを使用して他の方法を使用できます。ただし、アプリはデータのプロデューサーよりも消費者であることが多いため、通常、ストアへの変換はより頻繁に行われます。

Q3:アレイを使用して遭遇する問題は、ストレージの規則や非互換性に関する問題ではなく、パフォーマンスの問題です。

40
DDS

アプリの状態をデータベースと考えてください。

それが重要なアイデアです。

1)一意のIDを持つオブジェクトがあると、オブジェクトを参照するときに常にそのIDを使用できるため、アクションとリデューサー間でデータの最小量を渡す必要があります。 array.find(...)を使用するよりも効率的です。配列アプローチを使用すると、オブジェクト全体を渡す必要があり、すぐに乱雑になる可能性があるため、異なるレデューサー、アクション、またはコンテナ内でオブジェクトを再作成することになります(それは望ましくありません)。関連付けられたレデューサーにIDのみが含まれている場合でも、ビューは常にオブジェクト全体を取得できます。これは、状態をマッピングするときにコレクションを取得するためです(ビューは状態全体を取得してプロパティにマップするため)。私が言ったことのすべてのために、アクションは最小限のパラメーター量を持ち、最小限の情報量を減らし、試してみて、両方の方法を試してみると、アーキテクチャがよりスケーラブルでクリーンになりますコレクションにIDがある場合はID。

2)APIへの接続は、ストレージとリデューサーのアーキテクチャに影響を与えてはなりません。そのため、懸念の分離を維持するためのアクションがあります。再利用可能なモジュールでAPIの変換ロジックを出し入れし、APIを使用するアクションでそのモジュールをインポートするだけです。

3)IDを持つ構造に配列を使用しましたが、これは私が被った予期せぬ結果です:

  • コードを絶えず強化するオブジェクトの再作成
  • 必要な情報をレデューサーとアクションに渡す
  • その結果として、悪い、きれいではなく、スケーラブルでないコード。

私はデータ構造を変更し、多くのコードを書き直しました。 警告されています。トラブルに巻き込まれないでください。

また:

4)IDを持つほとんどのコレクションは、オブジェクト全体への参照としてIDを使用することを目的としているため、それを利用する必要があります。 API呼び出しはIDを取得しますそして残りのパラメーター、アクションとリデューサーも同様です。

11
Marco Scabbiolo

1)レデューサーのシンプルさは、IDによるオブジェクトキーイングアプローチを採用する動機ですか?その状態形状に他の利点はありますか?

キーとしてIDで保存されたオブジェクト(normalizedとも呼ばれる)にエンティティを保持し続ける主な理由は、作業が非常に煩わしいことです深くネストされたオブジェクト(より複雑なアプリのREST AP​​Iから通常取得するもの)—コンポーネントとレデューサーの両方に。

現在の例で正規化された状態の利点を説明するのは少し難しいです(深くネストされた構造を持たないため)。しかし、オプション(この例では)にもタイトルがあり、システム内のユーザーが作成したとしましょう。そうすると、応答は次のようになります。

[{
  id: 1,
  name: 'View',
  open: false,
  options: [
    {
      id: 10, 
      title: 'Option 10',
      created_by: { 
        id: 1, 
        username: 'thierry' 
      }
    },
    {
      id: 11, 
      title: 'Option 11',
      created_by: { 
        id: 2, 
        username: 'dennis'
      }
    },
    ...
  ],
  selectedOption: ['10'],
  parent: null,
},
...
]

次に、オプションを作成したすべてのユーザーのリストを表示するコンポーネントを作成するとします。これを行うには、最初にすべてのアイテムを要求し、次に各オプションを反復処理し、最後にcreated_by.usernameを取得する必要があります。

より良い解決策は、応答を次のように正規化することです。

results: [1],
entities: {
  filterItems: {
    1: {
      id: 1,
      name: 'View',
      open: false,
      options: [10, 11],
      selectedOption: [10],
      parent: null
    }
  },
  options: {
    10: {
      id: 10,
      title: 'Option 10',
      created_by: 1
    },
    11: {
      id: 11,
      title: 'Option 11',
      created_by: 2
    }
  },
  optionCreators: {
    1: {
      id: 1,
      username: 'thierry',
    },
    2: {
      id: 2,
      username: 'dennis'
    }
  }
}

この構造を使用すると、オプションを作成したすべてのユーザーを一覧表示するのがはるかに簡単かつ効率的になります(entities.optionCreatorsでそれらを分離しているので、そのリストをループするだけです)。

また、表示するのも非常に簡単です。 ID 1のフィルターアイテムのオプションを作成したユーザーのユーザー名:

entities
  .filterItems[1].options
  .map(id => entities.options[id])
  .map(option => entities.optionCreators[option.created_by].username)

2)IDによるオブジェクトキーイングアプローチは、APIの標準JSONの入出力に対処することを難しくしているようです。 (そのため、そもそもオブジェクトの配列を使用しました。)そのアプローチを使用する場合、関数を使用してJSON形式と状態形状形式の間で変換するだけですか?それは不格好なようです。 (もしあなたがそのアプローチを支持するなら、それは上記のオブジェクトの配列リデューサーよりも不格好ではないというあなたの推論の一部ですか?)

JSON応答は、たとえば normalizr

3)Dan Abramovは、理論的には状態データ構造にとらわれないようにreduxを設計したことを知っていますタイプ、「強調鉱山」)。しかし、上記のことを考えると、IDでキー設定されたオブジェクトを保持することが「推奨」されているのか、それとも中断するようにオブジェクトの配列を使用して実行する他の予期しない痛みポイントがありますかIDをキーとするオブジェクトを使用して計画を立てますか?

これはおそらく、深くネストされたAPI応答が多い、より複雑なアプリの推奨事項です。ただし、特定の例では、それほど重要ではありません。

8
tobiasandersen