-
리덕스 Normalizing State Shape :: 마이구미React 2020. 6. 4. 23:21반응형
이 글은 "리덕스를 어떻게 사용할 수 있는가?" 를 나타낸다.
여러 방법 중에 "Normalizing State Shape" 를 다룬다.
공식 문서에서 언급된 것으로, 리덕스를 활용하는 패턴의 하나라고 볼 수 있다.
전반적인 내용과 이를 프로젝트에 적용해본 후기를 작성하려한다.
관련 문서 - https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape글을 다루기에 앞서, 개발 시나리오를 생각해보자.
프로젝트 진행에 앞서, 서버쪽 API 가 아직 개발되지 않은 상태라면 프론트는 사전 작업을 먼저 하면 된다.
우선 마크업을 미리 진행하면 된다.
그리고 더 나아가 대략 추측 가능한 구조를 기반으로 더미 데이터를 API 응답으로 활용할 수 있다.
관련 작업에 있어, 이 글에서는 redux 를 이용한다.
즉, Redux 에서는 API 에 관련된 로직 및 상태를 관리하게된다.
API 응답이 어떻게 내려올지도 모르겠지만, 대부분 중첩된 구조로 내려올 것이다.
예를 들면 블로그 포스트들의 리스트가 존재한다면, 다음과 같이 내려올 것이다.
const blogPosts = [ { id: 'post1', author: { username: 'user1', name: 'User 1' }, body: '......', comments: [ { id: 'comment1', author: { username: 'user2', name: 'User 2' }, comment: '.....' }, { id: 'comment2', author: { username: 'user3', name: 'User 3' }, comment: '.....' } ] }, { id: 'post2', author: { username: 'user2', name: 'User 2' }, body: '......', comments: [ { id: 'comment3', author: { username: 'user3', name: 'User 3' }, comment: '.....' }, { id: 'comment4', author: { username: 'user1', name: 'User 1' }, comment: '.....' }, { id: 'comment5', author: { username: 'user3', name: 'User 3' }, comment: '.....' } ] } // and repeat many times ]
중첩된 구조는 댑스가 깊어질수록 상태 관리가 까다롭다는 것을 우리는 알고 있다.
예를 들어, username: 'user1' 을 값을 업데이트한다면, 어떻게 될까?
user1 을 가지고 있는 다른 요소를 찾아서 같이 업데이트를 해줘야한다.
결과적으로는 {post1}, {post2-comment4} 두 요소를 찾아서 똑같이 업데이트 해주어야한다.
즉, 다른 객체나 배열을 파헤치는 검색을 통해 동기를 맞춰줘야한다.
또 하나 더, 위 구조에서 id 값이 "comment4" 인 객체의 author 를 업데이트 할 경우는 어떻게 될까?
불변성을 위해 comment => comments => post => posts 모든 부모와 조상을 복사 및 업데이트를 새롭게 해줘야한다.
이로 인해, UI 컴포넌트는 비효율적인 리랜더링을 초래하게 된다.
이것만으로도 다른 잠재적인 것들을 대해 짐작할 수 있다.
이렇한 구조는 복잡해질수록 빠르게 어글리한 코드와 흐름을 맞이한다.
이를 위해 조금 더 효율적으로 state 를 리덕스에서 관리하는 방법을 고민해볼 수 있다.
공식문서에서 제공하는 것이 “Normalizing State Shape” 이다.
용어 그대로 데이터 구조를 노멀라이징하는 것이다.
핵심은 "구조를 어떻게 노멀라이징하는가?" 이다.
리덕스 스토어를 데이터베이스처럼 생각하고 테이블 구조를 갖추게 된다.
위 예제를 기반으로는 posts, comments, users 로 분류할 수 있다.
각 테이블의 아이템들은 key - value 구조의 형태를 가지게 된다.
{ posts : { byId : { "post1" : { id : "post1", author : "user1", body : "......", comments : ["comment1", "comment2"] }, "post2" : { id : "post2", author : "user2", body : "......", comments : ["comment3", "comment4", "comment5"] } }, allIds : ["post1", "post2"] }, comments : { byId : { "comment1" : { id : "comment1", author : "user2", comment : ".....", }, "comment2" : { id : "comment2", author : "user3", comment : ".....", }, "comment3" : { id : "comment3", author : "user3", comment : ".....", }, "comment4" : { id : "comment4", author : "user1", comment : ".....", }, "comment5" : { id : "comment5", author : "user3", comment : ".....", }, }, allIds : ["comment1", "comment2", "comment3", "commment4", "comment5"] }, users : { byId : { "user1" : { username : "user1", name : "User 1", }, "user2" : { username : "user2", name : "User 2", }, "user3" : { username : "user3", name : "User 3", } }, allIds : ["user1", "user2", "user3"] } }
각 테이블은 byId, allIds 키를 가진다.
byId 는 각 아이템의 고유 id 를 key 로 삼아 객체로 관리되어진다.
allIds 는 아이템들의 순서로 사용될 수 있다.
계층적보다는 평평한 구조를 가진다.
이 구조는 처음에 언급한 중첩된 구조와 비교하면 어떤 개선점이 있는가?
post, comment, user 는 각각 서로 다른 장소에 존재한다.
위에서 언급한대로 id 가 "user1" 인 아이템을 수정하는 경우가 발생한다면 어떻게 했었는가?
user1 을 가지는 모든 아이템들을 찾아서 각각 업데이트를 해야한다.
하지만 노멀라이징한 구조에서는 user 데이터는 user 에서만 관리하고 있다.
user 가 필요한 곳은 user 의 고유 id 만을 가진다. (마치 주소값만 들고 있는 형태)
결과적으로 user 에서 key 가 "user1" 인 아이템을 찾아서 업데이트만 하면 된다.
// 중첩된 구조 blogPosts.map((post) => { // post.author post.comments.map((comment) => { // comment.author } }) // 노멀라이징 users.byId['user1'] = newUserDatas;
중첩된 구조에서 탐색은 루프가 필수적이고, 구조의 뎁스에 따라서도 탐색 형태가 변하게 된다.
반대로 노멀라이징된 구조는 탐색이 굉장히 심플해지고, 일관성 있는 형태를 가질 수 있게 된다.
서로 다른 테이블의 아이템을 가지고 싶다면, 그 아이템의 고유 id 를 가지면서 이를 주소값을 참조하는 것처럼 활용한다.
중첩된 구조에서는 비록 author 만 업데이트했지만 불변성 유지를 위해 조상까지 모두 업데이트 해줘야했다.
노멀라이징된 구조를 보면, 각 post 는 author id 와 comment id 를 가지고, comment 는 author id 를 가진다.
서로 다른 타입이라면, 주소값을 참조하는 형태로 연결 관계를 만들어준다.
post, comment, author 는 같은 레벨로 여겨지고, 실제 데이터는 각자 다른 장소에서 관리되기 때문에 위 문제를 개선할 수 있다.
이를 기반으로 만들어진 라이브러리도 존재한다.
반응형'React' 카테고리의 다른 글
인스타그램 infinite scroll 파헤치기 :: 마이구미 (1) 2020.07.12 Redux 에 reselect 적용해보기 :: 마이구미 (1) 2020.06.07 Storybook 도입 (1) - 마이구미 (0) 2019.12.10 [React] Prompt 커스텀해서 제어하기 :: 마이구미 (0) 2019.10.03 create-react-app 에서 service worker 커스텀하기 :: 마이구미 (0) 2019.08.21