상태 모양 디자인 장에서 문서는 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로 키가 지정된 개체를 계획하고 유지하려고합니까?
답변
Q1 : 감속기의 단순성은 올바른 항목을 찾기 위해 배열을 검색 할 필요가 없기 때문입니다. 어레이를 검색 할 필요가 없다는 것이 장점입니다. 선택자 및 기타 데이터 접근자는 id
. 각 액세스에 대해 어레이를 검색해야하는 것은 성능 문제가됩니다. 어레이가 커지면 성능 문제가 급격히 악화됩니다. 또한 앱이 더 복잡해지고 더 많은 위치에서 데이터를 표시하고 필터링할수록 문제도 악화됩니다. 조합은 해로울 수 있습니다. 로 항목에 id
액세스하면 액세스 시간이에서 O(n)
로 변경되며 O(1)
, 이는 큰 경우 n
(여기서는 배열 항목) 큰 차이를 만듭니다.
Q2 : normalizr
API에서 스토어로의 변환을 지원하는 데 사용할 수 있습니다 . normalizr V3.1.0부터는 비정규 화를 사용하여 다른 방향으로 갈 수 있습니다. 즉, 앱은 종종 데이터 생산자보다 더 많은 소비자이므로 일반적으로 스토어로의 전환이 더 자주 수행됩니다.
Q3 : 어레이 사용시 발생하는 문제는 스토리지 규칙 및 / 또는 비 호환성 문제가 아니라 성능 문제입니다.
답변
앱의 상태를 데이터베이스로 생각하십시오.
그것이 핵심 아이디어입니다.
1) 고유 ID가있는 개체를 사용하면 개체를 참조 할 때 항상 해당 ID를 사용할 수 있으므로 작업과 감속기간에 최소한의 데이터를 전달해야합니다. array.find (…)를 사용하는 것보다 더 효율적입니다. 배열 접근 방식을 사용하는 경우 전체 개체를 전달해야하며 곧 지저분해질 수 있으며 다른 감속기, 작업 또는 컨테이너에서 개체를 다시 만들 수 있습니다 (원하지 않음). 뷰는 연결된 리듀서에 ID 만 포함되어 있어도 항상 전체 객체를 가져올 수 있습니다. 상태를 매핑 할 때 컬렉션을 어딘가에 가져 오기 때문입니다 (뷰는 전체 상태를 가져와 속성에 매핑합니다). 내가 말한 모든 것 때문에 액션은 최소한의 매개 변수를 가지게되고 최소한의 정보를 줄입니다. 한번 시도해보세요.
2) API에 대한 연결은 스토리지 및 감속기의 아키텍처에 영향을주지 않아야합니다. 그렇기 때문에 우려 사항을 분리하기위한 조치가 필요합니다. 재사용 가능한 모듈의 API 안팎으로 전환 로직을 넣고 API를 사용하는 작업에서 해당 모듈을 가져 오면됩니다.
3) ID가있는 구조에 배열을 사용했으며 다음과 같은 예기치 않은 결과가 발생했습니다.
- 코드 전체에서 지속적으로 객체 재생성
- 리듀서와 액션에 불필요한 정보 전달
- 그 결과, 나쁘고 깨끗하지 않고 확장 가능한 코드가 아닙니다.
결국 데이터 구조를 변경하고 많은 코드를 다시 작성했습니다. 경고를 받았으니 문제에 빠지지 마십시오.
또한:
4) ID가있는 대부분의 컬렉션은 전체 개체에 대한 참조로 ID를 사용하기위한 것이므로이를 활용해야합니다. API 호출은 ID 와 나머지 매개 변수를 가져 오므로 작업과 리듀서도 마찬가지입니다.
답변
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 응답이 많은 더 복잡한 앱에 대한 권장 사항 일 수 있습니다. 그러나 귀하의 특정 예에서는 그다지 중요하지 않습니다.