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 : ".....",
}
]
}
// 많이 반복
]
나같은 바보가 봤을땐 큰 문제가 없어보인다. 하지만 꼼꼼하고 자세히 들여다보면 몇몇 문제가 존재하는 걸 알수있다.
Object.is
로 행하기에, 상태가 업데이트되면 관계없는 컴포넌트까지 렌더링될 가능성이 높다. 즉, 중첩된 데이터는 적을 수록 좋다. 위와 같은 이유로 상태의 중첩을 잘 풀어내는 게중요하다. 여기서 잘 풀어내는 법이란 무엇일까? 과연 정해져있을까?
놀랍게도 어느정도는 정해져있다.
그리고 나는 몰랐다.😭
전공자거나, DB,SQL관련 공부를 한 분들은 이미 알고계실테지만 정규화는 데이터베이스 용어다.
중복을 최소화하게 데이터를 구조화 하는 방법, 이상이 있는 관계(릴레이션)을 재구성하여 작고 잘 조직된 관계를 생성한다
정규화의 종류는 생각보다 꽤 있다. 1형~6형 + BCNF 대충 7개정도 있다고 알고있는데, 자세하게 알지는 못한다.
일단 중복을 최소화 해야하기에 정규화가 필요하다는 것만 알고 넘어가자.
먼저, 정규화의 기본 개념은 아래와 같다.
위 개념을 이전의 복잡했던 상태에 적용해보자.
{
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"]
}
}
어떤 변화가 있었을까? 한 눈에 보기에도 상태의 구조가 확연히 달라졌다.
이제 각 상태는 독립적인 테이블을 가지게 되었다. 단순히 객체 내부에 id,author,body,comments
등 으로 존재하는게 아닌, 큰 줄기인 posts,comments,users
3가지로 나뉘게 되었다.
posts:{...},
comments:{...},
users:{...},
또한 각 데이터 항목은 id를 key로, 항목을 value로 가지는 개별 아이템 항목을 저장하게 되었다.
posts:{
byId:{
'post1':{...}
}
},
comments:{
byId:{
'comment1':{...}
}
},
users:{
byId:{
'user1':{...}
}
},
만약 특정 데이터를 id
기반으로 검색하려할때, 유용할 것 같다!
posts : {
byId : {
"post1" : {
id : "post1",
author : "user1",
body : "......",
comments : ["comment1", "comment2"]
},
"post2" : {
id : "post2",
author : "user2",
body : "......",
comments : ["comment3", "comment4", "comment5"]
}
}
post
는 author
와 comment
의 정보가 필요하다.
이때 전체 정보를 저장하는 것이 아닌, author
의 id와 comment
의 id만을 저장하여 참조한다.
만약 작성자의 데이터가 필요하다면, users.byId[author]
로 O(1)
의 시간에 접근이 가능하다.
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
만 변경하면 된다.이렇게 정규화된 상태는 많은 문제점들을 해결해준다. 뿐만아니라 컴포넌트 구조에도 영향을 끼칠수 밖에 없다.
이 부분은 DB에대한 지식이 미숙하여 DB를 공부하며 같이 다뤄보도록 하겠습니다.
상태 정규화를 하게 된 계기는 Youtube API를 이용해서 코멘트 데이터를 가져왔더니, id
로 접근하기가 어려웠다. 그래서 find
메서드를 계속 사용했는데, 이 메서드는 알다시피 배열을 모두 순회해야한다. 따라서 코멘트가 몇천개라면, find
가 들어있는 로직 당 몇천번을 순회하게된다. 이렇게 낭비되는 리소스도 있고 의도치 않은 컴포넌트 렌더링이 생각보다 잦았다. 이를 해결하기위해 상태를 정규화 하게되었다.
DB에대한 공부는 서버측만 필요하다고 생각해서 점점 미뤄왔는데, 역시 공부하지않을 핑곗거리가 필요했구나......😭
이제 미뤄왔던 DB에도 손을 대봐야겠다.
내용의 모든 출처는 redux공식문서 https://redux.js.org/usage/structuring-reducers/normalizing-state-shape 입니다.