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

     

    이를 기반으로 만들어진 라이브러리도 존재한다.

    https://github.com/paularmstrong/normalizr

    반응형

    댓글

Designed by Tistory.