(Redux) Normalizing State Shape

호두파파·2021년 5월 6일
0

Redux

목록 보기
6/6

프로젝트 진행에 앞서, 서버쪽 API가 아직 개발되지 않은 상태라면 프론트는 사전 작업을 먼저하게 된다.
마크업을 미리 진행하게 되고 더 나아가 대략 추측 가능한 구조를 기반으로 더미 데이터를 API 응답으로 활용할 수 있다. Redux를 통해 AIP에 관련된 로직 및 상태를 관리하게 된다.

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 
]
//출처: https://mygumi.tistory.com/373 [마이구미의 HelloWorld]

중첩된 구조는 댑스가 깊어질수록 상태 관리가 까다롭다는 것을 알수 있다.
예를 들어, username: 'user1'의 값을 업데이트 한다면, 어떻게 될까?
user1을 가지고 있는 다른 요소를 찾아서 같이 업데이트 해줘야 한다. 결과적으로 {post1}, {post2-comment} 두 요소를 찾아서 똑같이 업데이트 해주어야 한다. 즉, 다른 객체나 배열을 파헤치는 검색을 통해 동기를 맞춰줘야 한다.

추가적으로, 위 구조에서 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"]
    }
}
//출처: https://mygumi.tistory.com/373 [마이구미의 HelloWorld]

각 테이블은 byId, allIds 키를 가진다.
byId는 각 아이템의 고유 id를 key로 삼아 객체로 관리되어진다.
allIds는 아이템들의 순서로 사용될 수 있다.
계층적보다는 평평한 구조를 가진다.

중첩된 구조에 비해 갖는 장점

post, comment, user는 각각 서로 다른 장소에 존재한다.

위에서 언급한대로 id가 "user1"인 아이템을 수정하는 경우, user1을 가지는 모든 아이템들을 찾아서 각각 업데이트 해줘야 했따면, 노멀라이징한 구조에서는 user 데이터는 user dptjaks 관리하고 있다.

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 는 같은 레벨로 여겨지고, 실제 데이터는 각자 다른 장소에서 관리되기 때문에 위 문제를 개선할 수 있다.


출처

원문보기

profile
안녕하세요 주니어 프론트엔드 개발자 양윤성입니다.

0개의 댓글