TIL 109 - 상태 정규화

김영현·2024년 6월 29일
0

TIL

목록 보기
119/129

복잡한 상태

React에서 상태는 화면을 그리는데 사용된다. 다른 값들도 화면을 그릴때 사용되지만, 상태가 바뀌면, 렌더링이 무조건 일어나야함은 자명하다. (React에서 그렇게 고정해놨으니 자명인가?)

아무튼 이러한 상태는 가끔 방대한 데이터로 들어올 때가 있다. 만약 블로그 포스팅에 대한 상태라면 어떨까?

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 : ".....",
            }
        ]    
    }
    // 많이 반복
]

나같은 바보가 봤을땐 큰 문제가 없어보인다. 하지만 꼼꼼하고 자세히 들여다보면 몇몇 문제가 존재하는 걸 알수있다.

  • 상태를 업데이트할때 깊은 복사를 이용해야하는데, 구조가 복잡하여 깊은복사가 잘못이루질 수도 있다.
  • 상태가 중첩된다는건, 상태 업데이트 로직또한 복잡하고 중첩된다는 걸 의미한다. 특히 깊은 데이터를 업데이트할땐 코드가 지저분해질 가능성이 높다.
  • React에서 상태비교는 Object.is로 행하기에, 상태가 업데이트되면 관계없는 컴포넌트까지 렌더링될 가능성이 높다. 즉, 중첩된 데이터는 적을 수록 좋다.

위와 같은 이유로 상태의 중첩잘 풀어내는 게중요하다. 여기서 잘 풀어내는 법이란 무엇일까? 과연 정해져있을까?
놀랍게도 어느정도는 정해져있다.

그리고 나는 몰랐다.😭


정규화

전공자거나, DB,SQL관련 공부를 한 분들은 이미 알고계실테지만 정규화는 데이터베이스 용어다.

중복을 최소화하게 데이터를 구조화 하는 방법, 이상이 있는 관계(릴레이션)을 재구성하여 작고 잘 조직된 관계를 생성한다

정규화의 종류는 생각보다 꽤 있다. 1형~6형 + BCNF 대충 7개정도 있다고 알고있는데, 자세하게 알지는 못한다.
일단 중복을 최소화 해야하기에 정규화가 필요하다는 것만 알고 넘어가자.

먼저, 정규화의 기본 개념은 아래와 같다.

  1. 상태에서 각 데이터 타입은 자신의 테이블을 가진다.
  2. 데이터 테이블은 항목의 아이디를 key로, 항목들을 value로 가지는 개별 항목 아이템을 저장해야한다.
  3. 개별 항목에 대한 참조는 항목의 ID를 저장하여 수행한다.
  4. 배열의 ID는 순서를 나타내야 한다.

위 개념을 이전의 복잡했던 상태에 적용해보자.

{
    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"]
    }
}

어떤 변화가 있었을까? 한 눈에 보기에도 상태의 구조가 확연히 달라졌다.

1. 독립적인 테이블

이제 각 상태는 독립적인 테이블을 가지게 되었다. 단순히 객체 내부에 id,author,body,comments등 으로 존재하는게 아닌, 큰 줄기인 posts,comments,users 3가지로 나뉘게 되었다.

posts:{...},
comments:{...},
users:{...},

2. id를 key로, 항목을 value로 가지는 개별 아이템

또한 각 데이터 항목은 id를 key로, 항목을 value로 가지는 개별 아이템 항목을 저장하게 되었다.

posts:{
	byId:{
    	'post1':{...}
    }
},
comments:{
	byId:{
    	'comment1':{...}
    }
},
users:{
	byId:{
    	'user1':{...}
    }
},

만약 특정 데이터를 id기반으로 검색하려할때, 유용할 것 같다!

3. 개별 항목에 대한 참조는 항목의 ID를 저장하여 수행한다

posts : {
        byId : {
            "post1" : {
                id : "post1",
                author : "user1",
                body : "......",
                comments : ["comment1", "comment2"]    
            },
            "post2" : {
                id : "post2",
                author : "user2",
                body : "......",
                comments : ["comment3", "comment4", "comment5"]    
            }
        }

postauthorcomment의 정보가 필요하다.
이때 전체 정보를 저장하는 것이 아닌, author의 id와 comment의 id만을 저장하여 참조한다.
만약 작성자의 데이터가 필요하다면, users.byId[author]O(1)의 시간에 접근이 가능하다.

4. 배열의 ID로 표시 순서를 나타낸다.

posts:{
	byId:{
    	'post1':{...}
    },
    allIds:['post1', 'post2']
},
comments:{
	byId:{
    	'comment1':{...}
    },
   	allIds:['comment1', 'comment2']
},
users:{
	byId:{
    	'user1':{...}
    },
   allIds:['user1', 'user2']
},

각 데이터를 렌더링하려면, allIds를 순회하면 쉽게 가능해진다.

posts.allIds.map((postId) => posts.byId[postId]);

결과

이전의 상태보다 평평해져서 중첩된 데이터가 줄어들었다. 이전의 문제점들 또한 하나씩 개선되었다.

  • 각 아이템은 이제 한 곳에서만 정의되므로, 포스트를 업데이트하고싶다면 posts, 댓글이라면 comments만 변경하면 된다.
  • 깊은 수준의 중첩을 처리할 필요가 없어져서 상태 업데이트 로직이 간단해짐.
  • 특정 아이템을 검색하거나 업데이트하는 로직은 이전보다 훨씬 간단해졌다. id만을 이용해서 손쉽게 해결 가능하다.
  • 필요없는 컴포넌트가 렌더링 될 가능성이 줄어들었다.

이렇게 정규화된 상태는 많은 문제점들을 해결해준다. 뿐만아니라 컴포넌트 구조에도 영향을 끼칠수 밖에 없다.


번외) 관계형, 비관계형 데이터

이 부분은 DB에대한 지식이 미숙하여 DB를 공부하며 같이 다뤄보도록 하겠습니다.


느낀점

상태 정규화를 하게 된 계기는 Youtube API를 이용해서 코멘트 데이터를 가져왔더니, id로 접근하기가 어려웠다. 그래서 find메서드를 계속 사용했는데, 이 메서드는 알다시피 배열을 모두 순회해야한다. 따라서 코멘트가 몇천개라면, find가 들어있는 로직 당 몇천번을 순회하게된다. 이렇게 낭비되는 리소스도 있고 의도치 않은 컴포넌트 렌더링이 생각보다 잦았다. 이를 해결하기위해 상태를 정규화 하게되었다.

DB에대한 공부는 서버측만 필요하다고 생각해서 점점 미뤄왔는데, 역시 공부하지않을 핑곗거리가 필요했구나......😭
이제 미뤄왔던 DB에도 손을 대봐야겠다.


내용의 모든 출처는 redux공식문서 https://redux.js.org/usage/structuring-reducers/normalizing-state-shape 입니다.

profile
모르는 것을 모른다고 하기

0개의 댓글