Trạng thái dưới dạng mảng đối tượng so với đối tượng được khóa bởi id


94

Trong chương về Thiết kế Hình dạng Trạng thái , các tài liệu đề xuất giữ trạng thái của bạn trong một đối tượng được khóa bằng ID:

Giữ mọi thực thể trong một đối tượng được lưu trữ với ID làm khóa và sử dụng ID để tham chiếu nó từ các thực thể hoặc danh sách khác.

Họ tiếp tục trạng thái

Hãy coi trạng thái của ứng dụng như một cơ sở dữ liệu.

Tôi đang làm việc trên hình dạng trạng thái cho danh sách các bộ lọc, một số bộ lọc sẽ mở (chúng được hiển thị trong cửa sổ bật lên) hoặc có các tùy chọn đã chọn. Khi tôi đọc "Hãy nghĩ về trạng thái của ứng dụng như một cơ sở dữ liệu", tôi đã nghĩ về việc nghĩ chúng như một phản hồi JSON vì nó sẽ được trả về từ một API (chính nó được cơ sở dữ liệu hỗ trợ).

Vì vậy, tôi đã nghĩ về nó như là

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

Tuy nhiên, tài liệu đề xuất một định dạng giống như

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

Về lý thuyết, điều đó không thành vấn đề miễn là dữ liệu có thể tuần tự hóa (dưới tiêu đề "Trạng thái") .

Vì vậy, tôi đã hài lòng với phương pháp tiếp cận mảng đối tượng, cho đến khi tôi viết trình thu gọn của mình.

Với cách tiếp cận đối tượng-keyed-by-id (và sử dụng tự do cú pháp lây lan), OPEN_FILTERmột phần của trình giảm thiểu trở thành

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

Trong khi với cách tiếp cận mảng đối tượng, thì càng dài dòng hơn (và phụ thuộc vào hàm trợ giúp)

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

Vì vậy, câu hỏi của tôi gấp ba lần:

1) Sự đơn giản của bộ giảm thiểu có phải là động lực để sử dụng phương pháp tiếp cận đối tượng-keyed-by-id không? Có những ưu điểm nào khác đối với hình dạng trạng thái đó không?

2) Có vẻ như cách tiếp cận đối tượng-keyed-by-id khiến việc xử lý JSON tiêu chuẩn vào / ra cho một API khó hơn. (Đó là lý do tại sao tôi đã sử dụng mảng đối tượng ngay từ đầu.) Vì vậy, nếu bạn đi với cách tiếp cận đó, bạn có chỉ sử dụng một hàm để chuyển đổi nó qua lại giữa định dạng JSON và định dạng hình dạng trạng thái không? Điều đó có vẻ khó hiểu. (Mặc dù nếu bạn ủng hộ cách tiếp cận đó, thì có phải một phần lý do của bạn rằng nó ít rắc rối hơn trình giảm bớt mảng đối tượng ở trên?)

3) Tôi biết Dan Abramov đã thiết kế redux về mặt lý thuyết là bất khả tri về cấu trúc dữ liệu-trạng thái (như được gợi ý bởi "Theo quy ước, trạng thái cấp cao nhất là một đối tượng hoặc một số tập hợp khóa-giá trị khác như Bản đồ, nhưng về mặt kỹ thuật, nó có thể là bất kỳ gõ " nhấn mạnh của tôi). Nhưng với những điều trên, liệu có phải chỉ là "được khuyến nghị" giữ nó là một đối tượng được khóa bởi ID, hay có những điểm khó lường khác mà tôi sẽ gặp phải bằng cách sử dụng một mảng các đối tượng khiến nó trở nên như vậy mà tôi chỉ nên hủy bỏ nó. lập kế hoạch và cố gắng gắn bó với một đối tượng được khóa bởi ID?


2
Đây là một câu hỏi thú vị và tôi cũng đã hỏi, chỉ để cung cấp một số thông tin chi tiết, mặc dù tôi có xu hướng chuẩn hóa trong redux thay vì mảng (hoàn toàn vì tra cứu dễ dàng hơn), tôi thấy rằng nếu bạn sử dụng cách tiếp cận chuẩn hóa thì việc sắp xếp sẽ trở thành một vấn đề vì bạn không nhận được cấu trúc giống như mảng cung cấp cho bạn, vì vậy bạn buộc phải tự sắp xếp.
Robert Saunders

Tôi thấy có vấn đề trong cách tiếp cận 'object-keyed-by-id', tuy nhiên điều này không thường xuyên nhưng chúng tôi phải xem xét trường hợp này khi viết bất kỳ ứng dụng UI nào. Vì vậy, điều gì sẽ xảy ra nếu tôi muốn thay đổi thứ tự của thực thể bằng cách sử dụng phần tử kéo thả được liệt kê dưới dạng danh sách có thứ tự? Thông thường phương pháp tiếp cận 'object-keyed-by-id' không thành công ở đây và tôi chắc chắn sẽ sử dụng phương pháp tiếp cận theo mảng đối tượng để tránh các vấn đề rộng rãi như vậy. Có thể có nhiều nhưng nghĩ đến việc chia sẻ này đây
Kunal Navhate

Làm thế nào bạn có thể sắp xếp một đối tượng làm từ các đối tượng? Điều này dường như là không thể.
David Vielhuber

@DavidVielhuber Ý của bạn là ngoài việc sử dụng thứ gì đó như lodash sort_by? const sorted = _.sortBy(collection, 'attribute');
nickcoxdotme

Đúng. Hiện nay chúng tôi chuyển đổi các đối tượng vào các mảng bên trong một tài sản tính vue
David Vielhuber

Câu trả lời:


46

Q1: Tính đơn giản của bộ giảm thiểu là kết quả của việc không phải tìm kiếm trong mảng để tìm mục nhập phù hợp. Không phải tìm kiếm qua mảng là lợi thế. Người chọn và người truy cập dữ liệu khác có thể và thường truy cập các mục này bằng id. Việc phải tìm kiếm thông qua mảng cho mỗi lần truy cập trở thành một vấn đề về hiệu suất. Khi các mảng của bạn lớn hơn, vấn đề hiệu suất trở nên tồi tệ hơn. Ngoài ra, khi ứng dụng của bạn trở nên phức tạp hơn, hiển thị và lọc dữ liệu ở nhiều nơi hơn, vấn đề cũng trở nên tồi tệ hơn. Sự kết hợp có thể gây bất lợi. Khi truy cập các mục theo id, thời gian truy cập thay đổi từ O(n)thành O(1), đối với các mục lớn n(ở đây là mảng) tạo ra sự khác biệt rất lớn.

Câu hỏi 2: Bạn có thể sử dụng normalizrđể giúp bạn chuyển đổi từ API sang cửa hàng. Kể từ phiên bản normalizr V3.1.0, bạn có thể sử dụng chức năng không chuẩn hóa để đi theo cách khác. Điều đó nói rằng, Ứng dụng thường được nhiều người tiêu dùng hơn là nhà sản xuất dữ liệu và do đó, việc chuyển đổi sang lưu trữ thường được thực hiện thường xuyên hơn.

Câu hỏi 3: Các vấn đề bạn sẽ gặp phải khi sử dụng một mảng không phải là quá nhiều vấn đề với quy ước lưu trữ và / hoặc sự không tương thích, mà là nhiều vấn đề về hiệu suất hơn.


normalizer lại là thứ chắc chắn sẽ tạo ra nỗi đau khi chúng ta thay đổi độ lệch trong phần phụ trợ. Vì vậy, điều này luôn phải được cập nhật
Kunal Navhate

12

Hãy coi trạng thái của ứng dụng như một cơ sở dữ liệu.

Đó là ý tưởng chủ đạo.

1) Có các đối tượng có ID duy nhất cho phép bạn luôn sử dụng id đó khi tham chiếu đến đối tượng, vì vậy bạn phải chuyển lượng dữ liệu tối thiểu giữa các hành động và bộ giảm. Nó hiệu quả hơn sử dụng array.find (...). Nếu bạn sử dụng cách tiếp cận mảng, bạn phải chuyển toàn bộ đối tượng và điều đó có thể trở nên lộn xộn rất sớm, bạn có thể sẽ tạo lại đối tượng trên các bộ giảm, hành động khác nhau hoặc thậm chí trong vùng chứa (bạn không muốn điều đó). Chế độ xem sẽ luôn có thể nhận được đối tượng đầy đủ ngay cả khi trình thu gọn được liên kết của chúng chỉ chứa ID, bởi vì khi ánh xạ trạng thái, bạn sẽ nhận được tập hợp ở đâu đó (chế độ xem có toàn bộ trạng thái để ánh xạ nó với các thuộc tính). Vì tất cả những gì tôi đã nói, các hành động kết thúc với số lượng tham số tối thiểu và giảm lượng thông tin tối thiểu, hãy thử xem,

2) Kết nối với API không được ảnh hưởng đến kiến ​​trúc của bộ lưu trữ và bộ giảm tải của bạn, đó là lý do tại sao bạn phải thực hiện các hành động để tách biệt các mối quan tâm. Chỉ cần đặt logic chuyển đổi của bạn vào và ra khỏi API trong một mô-đun có thể tái sử dụng, nhập mô-đun đó vào các hành động sử dụng API và đó sẽ là điều đó.

3) Tôi đã sử dụng mảng cho các cấu trúc có ID và đây là những hậu quả không lường trước được mà tôi phải gánh chịu:

  • Tái tạo các đối tượng liên tục trong suốt mã
  • Chuyển thông tin không cần thiết đến các bộ giảm và hành động
  • Do đó, mã xấu, không sạch và không thể mở rộng.

Cuối cùng tôi đã thay đổi cấu trúc dữ liệu của mình và viết lại rất nhiều mã. Bạn đã được cảnh báo, xin đừng tự chuốc lấy rắc rối.

Cũng thế:

4) Hầu hết các bộ sưu tập có ID là để sử dụng ID làm tham chiếu cho toàn bộ đối tượng, bạn nên tận dụng điều đó. Các lệnh gọi API sẽ nhận được ID và sau đó là phần còn lại của các tham số, các hành động và bộ giảm của bạn cũng vậy.


Tôi đang gặp phải một vấn đề trong đó chúng tôi có một ứng dụng có nhiều dữ liệu (1000 đến 10.000) được lưu trữ bằng id trong một đối tượng trong cửa hàng redux. Trong các khung nhìn, chúng đều sử dụng các mảng đã sắp xếp để hiển thị dữ liệu chuỗi thời gian. Điều này có nghĩa là mỗi khi kết xuất xong, nó phải lấy toàn bộ đối tượng, chuyển đổi thành một mảng và sắp xếp nó. Tôi được giao nhiệm vụ cải thiện hiệu suất của ứng dụng. Đây có phải là một trường hợp sử dụng mà việc lưu trữ dữ liệu của bạn trong một mảng đã được sắp xếp và sử dụng tìm kiếm nhị phân để thực hiện xóa và cập nhật thay vì một đối tượng sẽ có ý nghĩa hơn không?
William Chou

Cuối cùng, tôi phải tạo một số bản đồ băm khác bắt nguồn từ dữ liệu này để giảm thiểu thời gian tính toán khi cập nhật. Điều này làm cho việc cập nhật tất cả các chế độ xem khác nhau yêu cầu logic cập nhật của riêng chúng. Trước đó, tất cả các thành phần sẽ lấy đối tượng từ cửa hàng và xây dựng lại cấu trúc dữ liệu mà nó cần để tạo chế độ xem của nó. Một cách mà tôi có thể nghĩ ra để đảm bảo tính nhạy cảm tối thiểu trong giao diện người dùng là sử dụng web worker để thực hiện chuyển đổi từ đối tượng sang mảng. Đánh đổi cho điều này là logic truy xuất và cập nhật đơn giản hơn vì tất cả các thành phần chỉ phụ thuộc vào một loại dữ liệu để đọc và ghi.
William Chou

8

1) Sự đơn giản của bộ giảm thiểu có phải là động lực để sử dụng phương pháp tiếp cận đối tượng-keyed-by-id không? Có những ưu điểm nào khác đối với hình dạng trạng thái đó không?

Lý do chính mà bạn muốn giữ các thực thể trong các đối tượng được lưu trữ với ID dưới dạng khóa (còn được gọi là chuẩn hóa ), là nó thực sự cồng kềnh khi làm việc với các đối tượng lồng nhau sâu (đó là những gì bạn thường nhận được từ API REST trong một ứng dụng phức tạp hơn) - cho cả các thành phần và bộ giảm tốc của bạn.

Hơi khó để minh họa lợi ích của trạng thái chuẩn hóa với ví dụ hiện tại của bạn (vì bạn không có cấu trúc lồng nhau sâu sắc ). Nhưng giả sử rằng các tùy chọn (trong ví dụ của bạn) cũng có tiêu đề và được tạo bởi người dùng trong hệ thống của bạn. Điều đó sẽ làm cho phản hồi trông giống như sau:

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

Bây giờ, giả sử bạn muốn tạo một thành phần hiển thị danh sách tất cả người dùng đã tạo các tùy chọn. Để làm được điều đó, trước tiên bạn phải yêu cầu tất cả các mục, sau đó lặp lại từng tùy chọn của chúng và cuối cùng lấy tên_người_dùng_tạo_sinh.

Một giải pháp tốt hơn sẽ là bình thường hóa phản hồi thành:

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

Với cấu trúc này, việc liệt kê tất cả người dùng đã tạo tùy chọn sẽ dễ dàng và hiệu quả hơn nhiều (chúng tôi đã phân lập họ trong entity.optionCreators, vì vậy chúng tôi chỉ cần lặp lại danh sách đó).

Nó cũng khá đơn giản để hiển thị ví dụ: tên người dùng của những người đã tạo tùy chọn cho mục bộ lọc với ID 1:

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

2) Có vẻ như cách tiếp cận đối tượng-keyed-by-id khiến việc xử lý JSON tiêu chuẩn vào / ra cho một API khó hơn. (Đó là lý do tại sao tôi đã sử dụng mảng đối tượng ngay từ đầu.) Vì vậy, nếu bạn đi với cách tiếp cận đó, bạn có chỉ sử dụng một hàm để chuyển đổi nó qua lại giữa định dạng JSON và định dạng hình dạng trạng thái không? Điều đó có vẻ khó hiểu. (Mặc dù nếu bạn ủng hộ cách tiếp cận đó, thì có phải một phần lý do của bạn rằng nó ít rắc rối hơn trình giảm bớt mảng đối tượng ở trên?)

Một phản hồi JSON có thể được chuẩn hóa bằng cách sử dụng ví dụ: normalizr .

3) Tôi biết Dan Abramov đã thiết kế redux về mặt lý thuyết là bất khả tri về cấu trúc dữ liệu-trạng thái (như được gợi ý bởi "Theo quy ước, trạng thái cấp cao nhất là một đối tượng hoặc một số tập hợp khóa-giá trị khác như Bản đồ, nhưng về mặt kỹ thuật, nó có thể là bất kỳ gõ "nhấn mạnh của tôi). Nhưng với những điều trên, có phải chỉ là "được khuyến nghị" để giữ nó là một đối tượng được khóa bởi ID, hay có những điểm khó lường khác mà tôi sẽ gặp phải bằng cách sử dụng một loạt các đối tượng khiến nó trở nên như vậy mà tôi chỉ nên hủy bỏ nó. lập kế hoạch và cố gắng gắn bó với một đối tượng được khóa bằng ID?

Đây có lẽ là một đề xuất cho các ứng dụng phức tạp hơn với nhiều phản hồi API lồng ghép sâu sắc. Tuy nhiên, trong ví dụ cụ thể của bạn, nó không thực sự quan trọng lắm.


1
maptrả về không xác định như ở đây , nếu tài nguyên được tìm nạp riêng biệt, làm cho filtercách này quá phức tạp. Có một giải pháp?
Saravanabalagi Ramachandran

1
@tobiasandersen Bạn có nghĩ rằng máy chủ trả về dữ liệu chuẩn hóa lý tưởng cho react / redux là ổn, để tránh máy khách thực hiện chuyển đổi thông qua libs như normalizr? Nói cách khác, làm cho máy chủ chuẩn hóa dữ liệu chứ không phải máy khách.
Matthew
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.