Nyatakan sebagai larik objek vs objek yang dikunci oleh id

97

Pada bab Mendesain Bentuk Status, dokumen menyarankan untuk mempertahankan status Anda dalam objek yang dikunci oleh ID:

Simpan setiap entitas dalam objek yang disimpan dengan ID sebagai kunci, dan gunakan ID untuk mereferensikannya dari entitas lain, atau daftar.

Mereka melanjutkan ke negara bagian

Pikirkan status aplikasi sebagai database.

Saya sedang mengerjakan bentuk status untuk daftar filter, beberapa di antaranya akan terbuka (ditampilkan dalam popup), atau telah memilih opsi. Saat saya membaca "Anggap status aplikasi sebagai database", saya berpikir untuk menganggapnya sebagai respons JSON karena akan dikembalikan dari API (yang didukung oleh database).

Jadi saya memikirkannya sebagai

[{
    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',
  }]

Namun, dokumen menyarankan format yang lebih mirip

{
   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',
  }
}

Secara teori, itu tidak masalah selama datanya dapat diserialkan (di bawah judul "State") .

Jadi saya pergi dengan pendekatan array-of-objects dengan senang hati, sampai saya menulis reducer saya.

Dengan pendekatan object-keyed-by-id (dan penggunaan liberal sintaks spread), OPEN_FILTERbagian reducer menjadi

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

Sedangkan dengan pendekatan array-of-objects, itu lebih verbose (dan fungsi pembantu bergantung)

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));
    }
    ...

Jadi pertanyaan saya ada tiga:

1) Apakah kesederhanaan peredam motivasi untuk menggunakan pendekatan object-keyed-by-id? Apakah ada keuntungan lain dari bentuk keadaan itu?

dan

2) Sepertinya pendekatan object-keyed-by-id membuatnya lebih sulit untuk menangani JSON standar masuk / keluar untuk API. (Itulah mengapa saya menggunakan array objek di tempat pertama.) Jadi jika Anda menggunakan pendekatan itu, apakah Anda hanya menggunakan fungsi untuk mengubahnya bolak-balik antara format JSON dan format bentuk negara? Sepertinya kikuk. (Meskipun jika Anda menganjurkan pendekatan itu, apakah bagian dari alasan Anda bahwa itu kurang kikuk daripada peredam array-objek di atas?)

dan

3) Saya tahu Dan Abramov mendesain redux agar secara teoritis menjadi status-data-struktur agnostik (seperti yang disarankan oleh "Berdasarkan konvensi, status tingkat atas adalah objek atau koleksi nilai kunci lainnya seperti Peta, tetapi secara teknis dapat berupa apa saja type , " tekankan milikku). Tetapi mengingat hal di atas, apakah hanya "disarankan" untuk menyimpannya sebagai objek yang dikunci oleh ID, atau adakah poin nyeri tak terduga lainnya yang akan saya hadapi dengan menggunakan berbagai objek yang membuatnya sedemikian rupa sehingga saya harus membatalkannya merencanakan dan mencoba untuk tetap dengan objek yang dikunci oleh ID?

nickcoxdotme
sumber
2
Ini adalah pertanyaan yang menarik, dan saya juga punya, hanya untuk memberikan beberapa wawasan, meskipun saya cenderung menormalkan di redux daripada array (murni karena mencari lebih mudah), saya menemukan bahwa jika Anda mengambil pendekatan yang dinormalisasi, penyortiran menjadi masalah karena Anda tidak mendapatkan struktur yang sama seperti yang diberikan larik, jadi Anda terpaksa mengurutkan diri sendiri.
Robert Saunders
Saya melihat masalah dalam pendekatan 'object-keyed-by-id', namun ini jarang terjadi tetapi kami harus mempertimbangkan kasus ini saat menulis aplikasi UI apa pun. Jadi bagaimana jika saya ingin mengubah urutan entitas menggunakan elemen seret yang terdaftar sebagai daftar yang dipesan? Biasanya pendekatan 'object-keyed-by-id' gagal di sini dan saya pasti akan menggunakan berbagai pendekatan objek untuk menghindari masalah yang begitu murah hati. Mungkin ada lebih banyak hal selain pemikiran untuk membagikan ini di sini
Kunal Navhate
Bagaimana Anda bisa menyortir objek yang terbuat dari objek? Ini sepertinya tidak mungkin.
David Vielhuber
@DavidVielhuber Maksudmu selain menggunakan sesuatu seperti lodash sort_by? const sorted = _.sortBy(collection, 'attribute');
nickcoxdotme
Iya. Saat ini kami mengonversi objek tersebut menjadi array di dalam properti komputasi vue
David Vielhuber

Jawaban:

47

T1: Kesederhanaan peredam adalah hasil dari tidak harus mencari melalui larik untuk menemukan entri yang benar. Keuntungannya tidak harus mencari melalui array. Penyeleksi dan pengakses data lainnya dapat dan sering mengakses item ini dengan id. Harus mencari melalui array untuk setiap akses menjadi masalah kinerja. Saat array Anda menjadi lebih besar, masalah kinerja memburuk dengan tajam. Selain itu, saat aplikasi Anda menjadi lebih kompleks, menampilkan dan memfilter data di lebih banyak tempat, masalahnya juga memburuk. Kombinasi tersebut bisa merugikan. Dengan mengakses item dengan id, waktu akses berubah dari O(n)menjadi O(1), yang untuk nitem besar (di sini array item) membuat perbedaan besar.

Q2: Anda dapat menggunakan normalizruntuk membantu Anda dengan konversi dari API ke toko. Pada normalizr V3.1.0 Anda dapat menggunakan denormalisasi untuk pergi ke arah lain. Meskipun demikian, Apps sering kali lebih menjadi konsumen daripada produsen data dan oleh karena itu konversi ke toko biasanya dilakukan lebih sering.

T3: Masalah yang akan Anda hadapi dengan menggunakan larik bukanlah banyak masalah dengan konvensi penyimpanan dan / atau ketidakcocokan, tetapi lebih banyak masalah kinerja.

DDS
sumber
Normalizer sekali lagi adalah hal yang pasti akan menimbulkan rasa sakit begitu kami mengubah def di backend. Jadi ini harus selalu diperbarui setiap saat
Kunal Navhate
12

Pikirkan status aplikasi sebagai database.

Itulah ide kuncinya.

1) Memiliki objek dengan ID unik memungkinkan Anda untuk selalu menggunakan id tersebut saat mereferensikan objek, jadi Anda harus meneruskan jumlah data minimum antara tindakan dan reduksi. Ini lebih efisien daripada menggunakan array.find (...). Jika Anda menggunakan pendekatan array, Anda harus meneruskan seluruh objek dan itu bisa segera menjadi berantakan, Anda mungkin akan membuat ulang objek tersebut pada reduksi, tindakan, atau bahkan dalam wadah yang berbeda (Anda tidak menginginkannya). Tampilan akan selalu bisa mendapatkan objek lengkap meskipun peredam terkaitnya hanya berisi ID, karena saat memetakan status Anda akan mendapatkan koleksi di suatu tempat (tampilan mendapatkan seluruh status untuk memetakannya ke properti). Karena semua yang saya katakan, tindakan akhirnya memiliki jumlah parameter minimal, dan mengurangi jumlah informasi minimal, cobalah,

2) Koneksi ke API seharusnya tidak memengaruhi arsitektur penyimpanan dan reduksi Anda, itulah mengapa Anda harus bertindak, untuk menjaga pemisahan masalah. Cukup masukkan logika konversi Anda ke dalam dan ke luar API dalam modul yang dapat digunakan kembali, impor modul tersebut dalam tindakan yang menggunakan API, dan seharusnya itu saja.

3) Saya menggunakan array untuk struktur dengan ID, dan ini adalah konsekuensi tak terduga yang saya derita:

  • Membuat ulang objek secara konstan di seluruh kode
  • Meneruskan informasi yang tidak perlu ke pereduksi dan tindakan
  • Akibatnya, kode buruk, tidak bersih dan tidak dapat diskalakan.

Saya akhirnya mengubah struktur data saya dan menulis ulang banyak kode. Anda telah diperingatkan, tolong jangan membuat diri Anda bermasalah.

Juga:

4) Sebagian besar koleksi dengan ID dimaksudkan untuk menggunakan ID sebagai referensi ke seluruh objek, Anda harus memanfaatkannya. Panggilan API akan mendapatkan ID dan parameter lainnya, begitu juga tindakan dan reduksi Anda.

Marco Scabbiolo
sumber
Saya menemukan masalah di mana kami memiliki aplikasi dengan banyak data (1000 hingga 10.000) yang disimpan oleh id dalam sebuah objek di toko redux. Dalam tampilan, mereka semua menggunakan array yang diurutkan untuk menampilkan data deret waktu. Ini berarti setiap kali render ulang selesai, itu harus mengambil seluruh objek, mengonversi ke array, dan mengurutkannya. Saya ditugaskan untuk meningkatkan kinerja aplikasi. Apakah ini kasus penggunaan di mana lebih masuk akal untuk menyimpan data Anda dalam array yang diurutkan dan menggunakan pencarian biner untuk melakukan penghapusan dan pembaruan daripada objek?
William Chou
Saya akhirnya harus membuat beberapa peta hash lain yang berasal dari data ini untuk meminimalkan waktu komputasi pada pembaruan. Hal ini membuat pembaruan semua tampilan yang berbeda membutuhkan logika pembaruannya sendiri. Sebelum ini, semua komponen akan mengambil objek dari penyimpanan, dan membangun kembali struktur data yang diperlukan untuk membuat tampilannya. Satu cara yang dapat saya pikirkan untuk memastikan minimal jankiness di UI menggunakan pekerja web untuk melakukan konversi dari objek ke array. Kompromi untuk ini adalah pengambilan dan pembaruan logika yang lebih sederhana karena semua komponen hanya bergantung pada satu jenis data untuk membaca dan menulis.
William Chou
8

1) Apakah kesederhanaan peredam motivasi untuk menggunakan pendekatan object-keyed-by-id? Apakah ada keuntungan lain dari bentuk keadaan itu?

Alasan utama Anda ingin menyimpan entitas dalam objek yang disimpan dengan ID sebagai kunci (juga disebut dinormalisasi ), adalah karena sangat rumit untuk bekerja dengan objek yang sangat bertingkat (yang biasanya Anda dapatkan dari REST API di aplikasi yang lebih kompleks) - baik untuk komponen dan reduksi Anda.

Agak sulit untuk mengilustrasikan manfaat dari status yang dinormalisasi dengan contoh Anda saat ini (karena Anda tidak memiliki struktur bersarang yang dalam ). Tapi katakanlah bahwa opsi (dalam contoh Anda) juga memiliki judul, dan dibuat oleh pengguna di sistem Anda. Itu akan membuat responsnya terlihat seperti ini:

[{
  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,
},
...
]

Sekarang katakanlah Anda ingin membuat komponen yang menampilkan daftar semua pengguna yang telah membuat opsi. Untuk melakukan itu, pertama-tama Anda harus meminta semua item, lalu mengulang setiap opsinya, dan terakhir mendapatkan create_by.username.

Solusi yang lebih baik adalah menormalkan respons menjadi:

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'
    }
  }
}

Dengan struktur ini, jauh lebih mudah, dan lebih efisien, untuk membuat daftar semua pengguna yang telah membuat opsi (kami mengisolasi mereka di entity.optionCreators, jadi kami hanya perlu mengulang daftar itu).

Ini juga cukup mudah untuk ditampilkan, misalnya nama pengguna yang telah membuat opsi untuk item filter dengan ID 1:

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

2) Sepertinya pendekatan object-keyed-by-id membuatnya lebih sulit untuk menangani JSON standar masuk / keluar untuk API. (Itulah mengapa saya menggunakan array objek di tempat pertama.) Jadi jika Anda menggunakan pendekatan itu, apakah Anda hanya menggunakan fungsi untuk mengubahnya bolak-balik antara format JSON dan format bentuk negara? Sepertinya kikuk. (Meskipun jika Anda menganjurkan pendekatan itu, apakah bagian dari alasan Anda bahwa itu kurang kikuk daripada peredam array-objek di atas?)

Respons JSON dapat dinormalisasi menggunakan mis . Normalizr .

3) Saya tahu Dan Abramov merancang redux agar secara teoritis menjadi status-data-struktur agnostik (seperti yang disarankan oleh "Berdasarkan konvensi, status tingkat atas adalah objek atau kumpulan nilai kunci lainnya seperti Peta, tetapi secara teknis dapat berupa apa saja type, "tekankan milikku). Tetapi mengingat hal di atas, apakah hanya "disarankan" untuk menyimpannya sebagai objek yang dikunci oleh ID, atau adakah poin nyeri tak terduga lainnya yang akan saya hadapi dengan menggunakan berbagai objek yang membuatnya sedemikian rupa sehingga saya harus membatalkannya merencanakan dan mencoba untuk tetap dengan objek yang dikunci oleh ID?

Ini mungkin merupakan rekomendasi untuk aplikasi yang lebih kompleks dengan banyak respons API yang sangat bertingkat. Dalam contoh khusus Anda, itu tidak terlalu penting.

tobiasandersen
sumber
2
mapmengembalikan tidak terdefinisi seperti di sini , jika sumber daya diambil secara terpisah, membuatnya filterterlalu rumit. Apakah ada solusinya?
Saravanabalagi Ramachandran
1
@tobiasandersen menurut Anda tidak masalah bagi server untuk mengembalikan data yang dinormalisasi yang ideal untuk react / redux, untuk menghindari klien melakukan konversi melalui libs seperti normalizr? Dengan kata lain, buat server menormalkan data dan bukan klien.
Matius