สถานะเป็นอาร์เรย์ของวัตถุเทียบกับวัตถุที่คีย์โดย id


97

ในบทที่เกี่ยวกับการออกแบบรูปร่างสถานะเอกสารแนะนำให้เก็บสถานะของคุณไว้ในอ็อบเจ็กต์ที่คีย์ด้วย ID:

เก็บทุกเอนทิตีในอ็อบเจ็กต์ที่จัดเก็บโดยมี ID เป็นคีย์และใช้ ID เพื่ออ้างอิงจากเอนทิตีหรือรายการอื่น

พวกเขาไปสู่สถานะ

คิดว่าสถานะของแอปเป็นฐานข้อมูล

ฉันกำลังทำงานเกี่ยวกับรูปร่างสถานะสำหรับรายการตัวกรองซึ่งบางส่วนจะเปิดอยู่ (แสดงในป๊อปอัป) หรือมีตัวเลือกที่เลือกไว้ เมื่อฉันอ่าน "คิดว่าสถานะของแอปเป็นฐานข้อมูล" ฉันคิดว่ามันเป็นการตอบสนอง JSON เนื่องจากจะส่งคืนจาก API (ฐานข้อมูลสนับสนุน)

ดังนั้นฉันคิดว่ามันเป็น

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

ในทางทฤษฎีมันไม่ควรเรื่องตราบเท่าที่ข้อมูล serializable (ภายใต้หัวข้อ "รัฐ")

ดังนั้นฉันจึงใช้วิธีอาร์เรย์ของวัตถุอย่างมีความสุขจนกระทั่งฉันเขียนตัวลด

ด้วยวิธี object-keyed-by-id (และการใช้ไวยากรณ์การแพร่กระจายแบบเสรี) OPEN_FILTERส่วนของตัวลดจะกลายเป็น

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

ในขณะที่วิธีอาร์เรย์ของอ็อบเจ็กต์จะยิ่งมี verbose มากขึ้น (และฟังก์ชันตัวช่วยพึ่งพา)

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

ดังนั้นคำถามของฉันมีสามเท่า:

1) ความเรียบง่ายของตัวลดแรงจูงใจในการดำเนินการด้วยวิธี object-keyed-by-id หรือไม่? มีข้อดีอื่น ๆ สำหรับรูปร่างของรัฐนั้นหรือไม่?

และ

2) ดูเหมือนว่าวิธี object-keyed-by-id ทำให้ยากต่อการจัดการกับ JSON มาตรฐานเข้า / ออกสำหรับ API (นั่นเป็นเหตุผลที่ฉันใช้อาร์เรย์ของวัตถุในตอนแรก) ดังนั้นถ้าคุณใช้แนวทางนั้นคุณเพียงแค่ใช้ฟังก์ชันเพื่อแปลงไปมาระหว่างรูปแบบ JSON และรูปแบบรูปร่างของรัฐหรือไม่? ดูเหมือนจะไม่เป็นระเบียบ (แม้ว่าคุณจะสนับสนุนแนวทางดังกล่าว แต่ก็เป็นส่วนหนึ่งของเหตุผลของคุณที่ว่ามันไม่เกะกะน้อยกว่าตัวลดอาร์เรย์ของวัตถุด้านบน?)

และ

3) ฉันรู้ว่า Dan Abramov ได้ออกแบบ redux ให้เป็นไปตามทฤษฎีโดยไม่เชื่อเรื่องพระเจ้า - โครงสร้างข้อมูล (ตามที่แนะนำโดย"ตามแบบแผนสถานะระดับบนสุดคือวัตถุหรือคอลเล็กชันคีย์ - ค่าอื่น ๆ เช่นแผนที่ แต่ในทางเทคนิคแล้วอาจเป็นได้ ประเภท "เน้นของฉัน) แต่จากที่กล่าวมาข้างต้นเป็นเพียง "แนะนำ" ให้เก็บวัตถุไว้ด้วย ID หรือมีจุดเจ็บปวดอื่น ๆ ที่คาดไม่ถึงที่ฉันจะพบโดยใช้อาร์เรย์ของวัตถุที่ทำให้เป็นเช่นนั้นฉันควรจะยกเลิกสิ่งนั้น วางแผนและพยายามที่จะยึดติดกับวัตถุที่คีย์ด้วย ID?


2
นี่เป็นคำถามที่น่าสนใจและเป็นคำถามที่ฉันมีเช่นกันเพื่อให้ข้อมูลเชิงลึกแม้ว่าฉันมักจะทำให้เป็นปกติใน redux แทนที่จะเป็นอาร์เรย์ (เพราะการค้นหานั้นง่ายกว่าเท่านั้น) ฉันพบว่าถ้าคุณใช้วิธีการเรียงลำดับแบบปกติจะกลายเป็น ปัญหาเนื่องจากคุณไม่ได้รับโครงสร้างเดียวกันกับที่อาร์เรย์ให้คุณดังนั้นคุณจึงถูกบังคับให้จัดเรียงตัวเอง
Robert Saunders

ฉันพบปัญหาในแนวทาง 'object-keyed-by-id' อย่างไรก็ตามสิ่งนี้เกิดขึ้นไม่บ่อยนัก แต่เราต้องพิจารณากรณีนี้ในขณะที่เขียนแอปพลิเคชัน UI ใด ๆ แล้วจะเกิดอะไรขึ้นถ้าฉันต้องการเปลี่ยนลำดับของเอนทิตีโดยใช้องค์ประกอบลากวางที่แสดงรายการตามลำดับ โดยปกติแล้ววิธีการ 'object-keyed-by-id' จะล้มเหลวที่นี่และฉันก็จะใช้วิธีการของวัตถุเพื่อหลีกเลี่ยงปัญหาที่กว้างขวางเช่นนี้ อาจมีมากกว่านี้ แต่คิดถึงการแบ่งปันที่นี่
Kunal Navhate

คุณจะจัดเรียงวัตถุที่สร้างจากวัตถุได้อย่างไร? ดูเหมือนจะเป็นไปไม่ได้
David Vielhuber

@DavidVielhuber คุณหมายถึงนอกจากใช้ของเช่น lodash sort_by? const sorted = _.sortBy(collection, 'attribute');
nickcoxdotme

ใช่. ขณะนี้เราแปลงวัตถุเหล่านั้นเป็นอาร์เรย์ภายในคุณสมบัติที่คำนวณโดย vue
David Vielhuber

คำตอบ:


47

Q1: ความเรียบง่ายของตัวลดคือผลจากการไม่ต้องค้นหาในอาร์เรย์เพื่อค้นหารายการที่ถูกต้อง ไม่ต้องค้นหาผ่านอาร์เรย์เป็นข้อดี Selectors และ accessors ข้อมูลอื่น ๆ idที่อาจจะทำและมักจะเข้าถึงรายการเหล่านี้โดย การต้องค้นหาอาร์เรย์สำหรับการเข้าถึงแต่ละครั้งกลายเป็นปัญหาด้านประสิทธิภาพ เมื่ออาร์เรย์ของคุณมีขนาดใหญ่ขึ้นปัญหาด้านประสิทธิภาพจะแย่ลงอย่างมาก นอกจากนี้เมื่อแอปของคุณมีความซับซ้อนมากขึ้นการแสดงและกรองข้อมูลในที่ต่างๆมากขึ้นปัญหาก็ยิ่งแย่ลงเช่นกัน การรวมกันอาจเป็นอันตรายได้ เมื่อเข้าถึงรายการโดยidเวลาในการเข้าถึงจะเปลี่ยนจากO(n)เป็นO(1)ซึ่งสำหรับรายการขนาดใหญ่n(ที่นี่อาร์เรย์) สร้างความแตกต่างอย่างมาก

Q2: คุณสามารถใช้normalizrเพื่อช่วยคุณในการแปลงจาก API เพื่อจัดเก็บ ในฐานะ normalizr V3.1.0 คุณสามารถใช้ denormalize เพื่อไปทางอื่น ที่กล่าวว่าแอพมักเป็นผู้บริโภคมากกว่าผู้ผลิตข้อมูลดังนั้นการแปลงเพื่อจัดเก็บจึงมักจะทำบ่อยกว่า

Q3: ปัญหาที่คุณจะพบโดยการใช้อาร์เรย์ไม่ใช่ปัญหามากนักเกี่ยวกับหลักการจัดเก็บข้อมูลและ / หรือความเข้ากันไม่ได้ แต่มีปัญหาด้านประสิทธิภาพมากกว่า


Normalizer เป็นอีกครั้งที่จะสร้างความเจ็บปวดเมื่อเราเปลี่ยน defs ในแบ็กเอนด์ ดังนั้นสิ่งนี้จะต้องอัปเดตอยู่เสมอ
Kunal Navhate

12

คิดว่าสถานะของแอปเป็นฐานข้อมูล

นั่นคือแนวคิดหลัก

1) การมีอ็อบเจ็กต์ที่มี ID เฉพาะช่วยให้คุณสามารถใช้ id นั้นได้เสมอเมื่ออ้างอิงอ็อบเจ็กต์ดังนั้นคุณต้องส่งผ่านจำนวนข้อมูลขั้นต่ำระหว่างการดำเนินการและตัวลด มีประสิทธิภาพมากกว่าการใช้ array.find (... ) หากคุณใช้วิธีอาร์เรย์คุณต้องส่งผ่านวัตถุทั้งหมดและสิ่งนั้นอาจยุ่งเหยิงในไม่ช้าคุณอาจต้องสร้างวัตถุขึ้นมาใหม่บนตัวลดการกระทำหรือแม้แต่ในคอนเทนเนอร์ (คุณไม่ต้องการสิ่งนั้น) มุมมองจะสามารถรับอ็อบเจ็กต์แบบเต็มได้เสมอแม้ว่าตัวลดที่เกี่ยวข้องจะมีเฉพาะ ID ก็ตามเนื่องจากเมื่อแมปสถานะคุณจะได้คอลเล็กชันที่ไหนสักแห่ง (มุมมองจะได้รับทั้งสถานะเพื่อแมปกับคุณสมบัติ เนื่องจากสิ่งที่ฉันพูดไปทั้งหมดการกระทำจึงมีพารามิเตอร์จำนวนน้อยที่สุดและลดข้อมูลจำนวนน้อยที่สุดลองดูสิ

2) การเชื่อมต่อกับ API ไม่ควรส่งผลกระทบต่อสถาปัตยกรรมของที่เก็บข้อมูลและตัวลดขนาดของคุณนั่นคือเหตุผลที่คุณต้องดำเนินการเพื่อแยกข้อกังวล เพียงใส่ตรรกะการแปลงของคุณเข้าและออกจาก API ในโมดูลที่ใช้ซ้ำได้นำเข้าโมดูลนั้นในการดำเนินการที่ใช้ API และนั่นก็ควรจะเป็นเช่นนั้น

3) ฉันใช้อาร์เรย์สำหรับโครงสร้างที่มี ID และนี่คือผลที่คาดไม่ถึงที่ฉันได้รับ:

  • สร้างวัตถุใหม่อย่างต่อเนื่องตลอดทั้งรหัส
  • ส่งผ่านข้อมูลที่ไม่จำเป็นไปยังตัวลดและการดำเนินการ
  • ด้วยเหตุนี้โค้ดที่ไม่ดีไม่สะอาดและไม่สามารถปรับขนาดได้

ฉันลงเอยด้วยการเปลี่ยนโครงสร้างข้อมูลและเขียนโค้ดใหม่จำนวนมาก คุณได้รับคำเตือนโปรดอย่าทำให้ตัวเองเดือดร้อน

นอกจากนี้:

4) คอลเลกชันส่วนใหญ่ที่มี ID มีจุดมุ่งหมายเพื่อใช้ ID เป็นข้อมูลอ้างอิงไปยังวัตถุทั้งหมดคุณควรใช้ประโยชน์จากสิ่งนั้น การเรียก API จะได้รับ ID จากนั้นพารามิเตอร์ที่เหลือก็จะดำเนินการและตัวลด


ฉันพบปัญหาที่เรามีแอปที่มีข้อมูลจำนวนมาก (1,000 ถึง 10,000) ที่จัดเก็บโดย id ในวัตถุในที่เก็บ redux ในมุมมองทั้งหมดใช้อาร์เรย์ที่เรียงลำดับเพื่อแสดงข้อมูลอนุกรมเวลา ซึ่งหมายความว่าทุกครั้งที่เรนเดอร์เสร็จสิ้นจะต้องใช้วัตถุทั้งหมดแปลงเป็นอาร์เรย์และเรียงลำดับ ฉันได้รับมอบหมายให้ปรับปรุงประสิทธิภาพของแอป นี่เป็นกรณีการใช้งานที่เหมาะสมกว่าในการจัดเก็บข้อมูลของคุณในอาร์เรย์ที่เรียงลำดับและใช้การค้นหาแบบไบนารีเพื่อทำการลบและอัปเดตแทนอ็อบเจ็กต์หรือไม่?
William Chou

ฉันต้องสร้างแผนที่แฮชอื่น ๆ ที่ได้มาจากข้อมูลนี้เพื่อลดเวลาประมวลผลในการอัปเดต ทำให้การอัปเดตมุมมองที่แตกต่างกันทั้งหมดต้องใช้ตรรกะการอัปเดตของตัวเอง ก่อนหน้านี้ส่วนประกอบทั้งหมดจะนำวัตถุออกจากที่เก็บและสร้างโครงสร้างข้อมูลที่จำเป็นเพื่อสร้างมุมมอง วิธีหนึ่งที่ฉันคิดได้เพื่อให้แน่ใจว่า UI มีปัญหาน้อยที่สุดคือการใช้ Web Worker เพื่อทำการแปลงจากออบเจ็กต์เป็นอาร์เรย์ การแลกเปลี่ยนสำหรับสิ่งนี้คือการดึงข้อมูลและการอัปเดตตรรกะที่ง่ายกว่าเนื่องจากส่วนประกอบทั้งหมดขึ้นอยู่กับข้อมูลประเภทเดียวที่จะอ่านและเขียน
William Chou

8

1) ความเรียบง่ายของตัวลดแรงจูงใจในการดำเนินการด้วยวิธี object-keyed-by-id หรือไม่? มีข้อดีอื่น ๆ สำหรับรูปร่างของรัฐนั้นหรือไม่?

เหตุผลหลักที่คุณต้องการเก็บเอนทิตีในอ็อบเจ็กต์ที่จัดเก็บด้วย ID เป็นคีย์ (หรือที่เรียกว่าnormalized ) ก็คือการทำงานกับอ็อบเจ็กต์ที่ซ้อนกันลึก ๆ (ซึ่งโดยทั่วไปแล้วคุณจะได้รับจาก 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 ทำให้ยากต่อการจัดการกับ JSON มาตรฐานเข้า / ออกสำหรับ API (นั่นเป็นเหตุผลที่ฉันใช้อาร์เรย์ของวัตถุในตอนแรก) ดังนั้นถ้าคุณใช้แนวทางนั้นคุณเพียงแค่ใช้ฟังก์ชันเพื่อแปลงไปมาระหว่างรูปแบบ JSON และรูปแบบรูปร่างของรัฐหรือไม่? ดูเหมือนจะไม่เป็นระเบียบ (แม้ว่าคุณจะสนับสนุนแนวทางดังกล่าว แต่ก็เป็นส่วนหนึ่งของเหตุผลของคุณที่ว่ามันไม่เกะกะน้อยกว่าตัวลดอาร์เรย์ของวัตถุด้านบน?)

JSON การตอบสนองสามารถใช้ปกติเช่นnormalizr

3) ฉันรู้ว่า Dan Abramov ได้ออกแบบ redux ให้เป็นไปตามทฤษฎีโดยไม่เชื่อเรื่องพระเจ้า - โครงสร้างข้อมูล (ตามที่แนะนำโดย "ตามแบบแผนสถานะระดับบนสุดคือวัตถุหรือคอลเล็กชันคีย์ - ค่าอื่น ๆ เช่นแผนที่ แต่ในทางเทคนิคแล้วอาจเป็นได้ ประเภท "เน้นของฉัน) แต่จากที่กล่าวมาข้างต้นเป็นเพียง "แนะนำ" ให้เก็บวัตถุไว้ด้วย ID หรือมีจุดเจ็บปวดอื่น ๆ ที่คาดไม่ถึงที่ฉันจะพบโดยใช้อาร์เรย์ของวัตถุที่ทำให้เป็นเช่นนั้นฉันควรจะยกเลิกสิ่งนั้น วางแผนและพยายามที่จะยึดติดกับวัตถุที่คีย์ด้วย ID?

อาจเป็นคำแนะนำสำหรับแอปที่ซับซ้อนมากขึ้นพร้อมการตอบสนอง API ที่ซ้อนกันอย่างลึกซึ้ง ในตัวอย่างเฉพาะของคุณมันไม่ได้สำคัญขนาดนั้น


2
mapส่งกลับไม่ได้กำหนดไว้ในที่นี้หากมีการดึงทรัพยากรแยกจากกันทำให้filterซับซ้อนเกินไป มีวิธีแก้ไขไหม
Saravanabalagi Ramachandran

1
@tobiasandersen คุณคิดว่าเป็นเรื่องปกติหรือไม่ที่เซิร์ฟเวอร์จะส่งคืนข้อมูลปกติที่เหมาะสำหรับ react / redux เพื่อหลีกเลี่ยงไม่ให้ไคลเอ็นต์ทำการแปลงผ่าน libs เหมือน normalizr? กล่าวอีกนัยหนึ่งคือทำให้เซิร์ฟเวอร์ทำให้ข้อมูลเป็นปกติไม่ใช่ไคลเอนต์
Matthew
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.