개체의 배열과 ID로 키가 지정된 개체의 상태


94

상태 모양 디자인 장에서 문서는 ID로 키가 지정된 객체에 상태를 유지하도록 제안합니다.

ID와 함께 저장된 개체의 모든 항목을 키로 유지하고 ID를 사용하여 다른 항목이나 목록에서 참조합니다.

그들은 상태로 이동

앱의 상태를 데이터베이스로 생각하십시오.

필터 목록에 대한 상태 모양을 작업 중이며 일부는 열리거나 (팝업에 표시됨) 옵션을 선택했습니다. "Think of the app 's state as a database"를 읽었을 때 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 접근 방식 (및 확산 구문의 자유로운 사용)을 사용 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));
    }
    ...

그래서 내 질문은 세 가지입니다.

1) 감속기의 단순성이 object-keyed-by-id 접근 방식을 사용하는 동기입니까? 그 상태 모양에 다른 이점이 있습니까?

2) id에 의한 object-keyed 접근 방식은 API에 대한 표준 JSON 입출력을 처리하는 것을 더 어렵게하는 것 같습니다. (이것이 제가 처음에 객체 배열을 사용하는 이유입니다.) 따라서 이러한 접근 방식을 사용한다면 함수를 사용하여 JSON 형식과 상태 모양 형식간에 앞뒤로 변환합니까? 투박해 보입니다. (당신이 그 접근 방식을 옹호한다면, 위의 객체 배열 감속기보다 덜 투박하다는 추론의 일부입니까?)

3) Dan Abramov가 이론적으로 상태 데이터 구조에 구애받지 않도록 redux를 설계 한 것을 알고 있습니다 ( "관례 상 최상위 상태는 객체 또는지도와 같은 다른 키-값 컬렉션이지만 기술적으로는 type , " 내 강조). 그러나 위의 경우 ID로 키가 지정된 개체를 유지하는 것이 "권장"입니까, 아니면 중단해야하는 개체 배열을 사용하여 실행할 다른 예상치 못한 문제점이 있습니까? ID로 키가 지정된 개체를 계획하고 유지하려고합니까?


2
이것은 흥미로운 질문이며, 약간의 통찰력을 제공하기 위해 제가 가지고있는 질문입니다. 비록 제가 배열 대신에 redux에서 정규화하는 경향이 있지만 (순전히 조회가 더 쉽기 때문에), 정규화 된 접근 방식 정렬을 취하면 배열이 제공하는 것과 동일한 구조를 얻지 못하기 때문에 문제가 발생하므로 강제로 정렬해야합니다.
Robert Saunders

'object-keyed-by-id'접근 방식에 문제가 있지만 자주 발생하지는 않지만 UI 애플리케이션을 작성하는 동안이 경우를 고려해야합니다. 그러면 정렬 된 목록으로 나열된 드래그 드롭 요소를 사용하여 엔티티의 순서를 변경하려면 어떻게해야합니까? 일반적으로 'object-keyed-by-id'접근 방식은 여기서 실패하며 이러한 관대 한 문제를 피하기 위해 객체 접근 방식의 배열을 확실히 사용합니다. 가 더 할 수 있지만, 여기를 공유하고 생각을 할 수
쿠날 Navhate을

개체로 만든 개체를 어떻게 정렬 할 수 있습니까? 불가능 해 보입니다.
David Vielhuber

@DavidVielhuber 당신은 lodash와 같은 것을 사용하는 것 외에 의미 sort_by합니까? const sorted = _.sortBy(collection, 'attribute');
nickcoxdotme

예. 현재 우리는 이러한 객체를 vue 계산 속성 내부의 배열로 변환합니다
David Vielhuber

답변:


46

Q1 : 감속기의 단순성은 올바른 항목을 찾기 위해 배열을 검색 할 필요가 없기 때문입니다. 어레이를 검색 할 필요가 없다는 것이 장점입니다. 선택자 및 기타 데이터 접근자는 id. 각 액세스에 대해 어레이를 검색해야하는 것은 성능 문제가됩니다. 어레이가 커지면 성능 문제가 급격히 악화됩니다. 또한 앱이 더 복잡해지고 더 많은 위치에서 데이터를 표시하고 필터링할수록 문제도 악화됩니다. 조합은 해로울 수 있습니다. 로 항목에 id액세스하면 액세스 시간이에서 O(n)로 변경되며 O(1), 이는 큰 경우 n(여기서는 배열 항목) 큰 차이를 만듭니다.

Q2 : normalizrAPI에서 스토어로의 변환을 지원하는 데 사용할 수 있습니다 . normalizr V3.1.0부터는 비정규 화를 사용하여 다른 방향으로 갈 수 있습니다. 즉, 앱은 종종 데이터 생산자보다 더 많은 소비자이므로 일반적으로 스토어로의 전환이 더 자주 수행됩니다.

Q3 : 어레이 사용시 발생하는 문제는 스토리지 규칙 및 / 또는 비 호환성 문제가 아니라 성능 문제입니다.


normalizer는 백엔드에서 정의를 변경하면 확실히 고통을 유발할 것입니다. 이 날짜마다 주기적으로 업데이트한다 그래서
쿠날 Navhate

12

앱의 상태를 데이터베이스로 생각하십시오.

그것이 핵심 아이디어입니다.

1) 고유 ID가있는 개체를 사용하면 개체를 참조 할 때 항상 해당 ID를 사용할 수 있으므로 작업과 감속기간에 최소한의 데이터를 전달해야합니다. array.find (...)를 사용하는 것보다 더 효율적입니다. 배열 접근 방식을 사용하는 경우 전체 개체를 전달해야하며 곧 지저분해질 수 있으며 다른 감속기, 작업 또는 컨테이너에서 개체를 다시 만들 수 있습니다 (원하지 않음). 뷰는 연결된 리듀서에 ID 만 포함되어 있어도 항상 전체 객체를 가져올 수 있습니다. 상태를 매핑 할 때 컬렉션을 어딘가에 가져 오기 때문입니다 (뷰는 전체 상태를 가져와 속성에 매핑합니다). 내가 말한 모든 것 때문에 액션은 최소한의 매개 변수를 가지게되고 최소한의 정보를 줄입니다. 한번 시도해보세요.

2) API에 대한 연결은 스토리지 및 감속기의 아키텍처에 영향을주지 않아야합니다. 그렇기 때문에 우려 사항을 분리하기위한 조치가 필요합니다. 재사용 가능한 모듈의 API 안팎으로 전환 로직을 넣고 API를 사용하는 작업에서 해당 모듈을 가져 오면됩니다.

3) ID가있는 구조에 배열을 사용했으며 다음과 같은 예기치 않은 결과가 발생했습니다.

  • 코드 전체에서 지속적으로 객체 재생성
  • 리듀서와 액션에 불필요한 정보 전달
  • 그 결과, 나쁘고 깨끗하지 않고 확장 가능한 코드가 아닙니다.

결국 데이터 구조를 변경하고 많은 코드를 다시 작성했습니다. 경고를 받았으니 문제에 빠지지 마십시오.

또한:

4) ID가있는 대부분의 컬렉션은 전체 개체에 대한 참조로 ID를 사용하기위한 것이므로이를 활용해야합니다. API 호출은 ID 나머지 매개 변수를 가져 오므로 작업과 리듀서도 마찬가지입니다.


redux 저장소의 개체에 ID로 저장된 많은 데이터 (1000 ~ 10,000)가있는 앱이있는 문제를 발견했습니다. 뷰에서는 모두 정렬 된 배열을 사용하여 시계열 데이터를 표시합니다. 즉, rerender가 완료 될 때마다 전체 개체를 가져 와서 배열로 변환하고 정렬해야합니다. 나는 앱의 성능을 향상시키는 임무를 맡았습니다. 데이터를 정렬 된 배열에 저장하고 이진 검색을 사용하여 객체 대신 삭제 및 업데이트를 수행하는 것이 더 합당한 사용 사례입니까?
William Chou

결국 업데이트에 대한 컴퓨팅 시간을 최소화하기 위해이 데이터에서 파생 된 다른 해시 맵을 만들어야했습니다. 따라서 모든 다른 뷰를 업데이트하려면 자체 업데이트 논리가 필요합니다. 이전에는 모든 구성 요소가 저장소에서 개체를 가져 와서보기를 만드는 데 필요한 데이터 구조를 다시 작성했습니다. UI에서 흔들림을 최소화하기 위해 제가 생각할 수있는 한 가지 방법은 웹 작업자를 사용하여 객체에서 배열로 변환하는 것입니다. 이에 대한 트레이드 오프는 모든 구성 요소가 읽고 쓸 데이터 유형에 의존하기 때문에 더 간단한 검색 및 업데이트 논리입니다.
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'
    }
  }
}

이 구조를 사용하면 옵션을 만든 모든 사용자를 나열하는 것이 훨씬 쉽고 효율적입니다 (엔티티 .optionCreators에 격리되어 있으므로 해당 목록을 반복하면됩니다).

예를 들어 ID 1의 필터 항목에 대한 옵션을 생성 한 사용자의 사용자 이름을 표시하는 것도 매우 간단합니다.

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

2) id에 의한 object-keyed 접근 방식은 API에 대한 표준 JSON 입출력을 처리하는 것을 더 어렵게하는 것 같습니다. (이것이 제가 처음에 객체 배열을 사용하는 이유입니다.) 따라서 이러한 접근 방식을 사용한다면 함수를 사용하여 JSON 형식과 상태 모양 형식간에 앞뒤로 변환합니까? 투박해 보입니다. (당신이 그 접근 방식을 옹호한다면, 위의 객체 배열 감속기보다 덜 투박하다는 추론의 일부입니까?)

JSON 응답은 예를 사용하여 정규화 될 수 normalizr .

3) Dan Abramov가 이론적으로 상태 데이터 구조에 구애받지 않도록 redux를 설계 한 것을 알고 있습니다 ( "관례 상 최상위 상태는 객체 또는지도와 같은 다른 키-값 컬렉션이지만 기술적으로는 유형, "내 강조). 그러나 위의 경우 ID로 키가 지정된 개체를 유지하는 것이 "권장"입니까, 아니면 중단해야하는 개체 배열을 사용하여 실행할 수있는 다른 예상치 못한 문제점이 있습니까? ID로 키가 지정된 개체를 계획하고 유지하려고합니까?

깊이 중첩 된 API 응답이 많은 더 복잡한 앱에 대한 권장 사항 일 수 있습니다. 그러나 귀하의 특정 예에서는 그다지 중요하지 않습니다.


1
map리소스를 별도로 가져 오는 경우 여기 에서 와 같이 undefined를 반환 하므로 filter너무 복잡해집니다. 해결책이 있습니까?
Saravanabalagi Ramachandran

1
@tobiasandersen 서버가 react / redux에 이상적인 정규화 된 데이터를 반환하여 클라이언트가 normalizr과 같은 libs를 통해 변환을 수행하지 않도록하는 것이 괜찮다고 생각하십니까? 즉, 서버가 클라이언트가 아닌 데이터를 정규화하도록합니다.
Matthew
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.