オブジェクトの配列とIDでキー設定されたオブジェクトの状態


94

ステートシェイプの設計に関する章のドキュメントでは、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',
  }
}

理論的にデータがシリアル化可能である限り( "State"という見出しの下)は問題になりません。

そのため、レデューサーを作成するまで、オブジェクトの配列のアプローチを楽しく使用しました。

object-keyed-by-idアプローチ(および、spread構文の自由な使用)では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)レデューサーのシンプルさが、object-keyed-by-idアプローチを採用する動機ですか?その状態の形に他の利点はありますか?

そして

2)id-object-keyed-by-idアプローチでは、APIの標準のJSONイン/アウトの処理が難しくなるようです。(それが、私が最初にオブジェクトの配列を使用した理由です。)したがって、そのアプローチを使用する場合は、関数を使用して、JSON形式と状態形状形式の間で相互に変換しますか?それは不格好のようです。(もしあなたがそのアプローチを支持しているなら、それはあなたの推論の一部であり、それは上記のオブジェクトの配列のレデューサーよりも不格好ではないですか?)

そして

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


2
これは興味深い質問です。私が知っていたのは、洞察を提供するためだけですが、配列の代わりにreduxで正規化する傾向があります(たぶん、ルックアップが簡単だからです)、正規化されたアプローチを取ると、ソートが次のようになります。配列が提供するものと同じ構造を取得できないため、自分でソートする必要があるため、問題が発生します。
ロバートサンダース

'object-keyed-by-id'アプローチに問題があるように見えますが、これは頻繁ではありませんが、UIアプリケーションを作成する際にこのケースを考慮する必要があります。では、順序付きリストとしてリストされているドラッグドロップ要素を使用してエンティティの順序を変更したい場合はどうなりますか?通常、 'object-keyed-by-id'アプローチはここでは失敗します。そのような寛大な問題を回避するために、私は確かに一連のオブジェクトアプローチを使用します。そこよりもよいが、ここではこれを共有していると考えることができ
クナルNavhateを

オブジェクトでできているオブジェクトをどのように並べ替えることができますか?これは不可能のようです。
David Vielhuber

@DavidVielhuberあなたはlodashのようなものを使用する以外に意味sort_byですか?const sorted = _.sortBy(collection, 'attribute');
nickcoxdotme 2018

はい。現在、これらのオブジェクトをvue計算プロパティ内の配列に変換します
David Vielhuber

回答:


46

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

Q2:normalizrAPIからストアへの変換を支援するために使用できます。normalizr V3.1.0以降では、非正規化を使用して他の方法に進むことができます。とはいえ、アプリはデータのプロデューサーよりも消費者であることが多く、そのため、ストアへの変換は通常より頻繁に行われます。

Q3:配列を使用して遭遇する問題は、ストレージの慣習や互換性の問題ではなく、パフォーマンスの問題です。


ノーマライザは、バックエンドでdefを変更すると、きっと苦痛をもたらすものです。したがって、これは毎回最新の状態を維持する必要があります
Kunal Navhate '06 / 08/05

12

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

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

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

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

3)IDのある構造に配列を使用しましたが、これらは私が経験した予期せぬ結果です:

  • コード全体で常にオブジェクトを再作成する
  • 不要な情報をレデューサーとアクションに渡す
  • その結果として、悪い、クリーンでなく、スケーラブルでないコードです。

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

また:

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


アプリに大量のデータ(1000から10,000)があり、reduxストアのオブジェクトにIDで保存されているという問題に遭遇しました。ビューでは、すべて並べ替えられた配列を使用して時系列データを表示します。つまり、再レンダリングが行われるたびに、オブジェクト全体を取得して配列に変換し、並べ替える必要があります。アプリのパフォーマンスを改善する必要がありました。これは、並べ替えられた配列にデータを格納し、オブジェクトの代わりにバイナリ検索を使用して削除と更新を行うほうが理にかなっているユースケースですか?
William Chou

更新の計算時間を最小限に抑えるために、このデータから他のいくつかのハッシュマップを作成する必要がありました。これにより、すべての異なるビューを更新するには、独自の更新ロジックが必要になります。これ以前は、すべてのコンポーネントがストアからオブジェクトを取得し、ビューを作成するために必要なデータ構造を再構築していました。UIのごちゃごちゃを最小限に抑えるには、オブジェクトから配列への変換にWebワーカーを使用するのが1つの方法です。これと引き換えに、すべてのコンポーネントが読み取りと書き込みを行う1つのタイプのデータに依存するだけなので、検索と更新のロジックが単純になります。
William Chou

8

1)レデューサーのシンプルさが、object-keyed-by-idアプローチを採用する動機ですか?その状態の形に他の利点はありますか?

IDをキーとして格納されたオブジェクト(正規化とも呼ばれる)にエンティティを保持したい主な理由は、深くネストされたオブジェクト(通常、より複雑なアプリのREST APIから取得するもの)を操作するのが非常に面倒だからです。あなたのコンポーネントとあなたの減速機の両方のために。

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

[{
  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)object-keyed-by-idアプローチでは、APIの標準のJSONイン/アウトの処理が難しくなるようです。(それが、私が最初にオブジェクトの配列を使用した理由です。)したがって、そのアプローチを使用する場合、関数を使用して、JSON形式と状態形状形式の間で変換するだけですか。それは不格好のようです。(もしあなたがそのアプローチを支持しているなら、それはあなたの推論の一部であり、それは上記のオブジェクトの配列のレデューサーよりも不格好ではないですか?)

JSON応答は、たとえばnormalizrを使用して正規化できます。

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

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


1
mapリソースが個別にフェッチされる場合、ここでのようにundefinedを返しますfilter。解決策はありますか?
サラバナバラギラマチャンドラン

1
@tobiasandersenクライアントがnormalizrのようなライブラリを介して変換を行わないようにするために、react / reduxに最適な正規化されたデータをサーバーが返すことは問題ないと思いますか?つまり、クライアントではなくサーバーでデータを正規化します。
マシュー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.