habit.jsx파일에 와서 Component를 PureComponent로 바꿔주고 플러스 버튼을 눌러줘도 App창에서 업데이트가 되지 않는다.
왜그러냐면 shallow comparison 때문에 그런데 Habit라는 것은 자체적인 state는 없고 props를 전달받는 데, props안에 데이터가 변경 되지 않으면 render()가 호출 되지 않는게 PureComponent이다. props안에 habit가 있고 각각의 콜백함수가 전달되어지는데, (onIncrement, onDecrememnt, onDelete)
각각의 콜백함수는 결국 App에서 전달되는 건데 앱 클래스 Component 안에 선언된 handleIncrement나 이런 멤버변수같은 것들이 전달되기 때문에 한번 App이라는 클래스가 만들어 진 이후로는 절대 변경되지 않아서 props들은 변하지 않는다. 카운트를 증가하게 되면 habit안에 있는 카운트만 변하게 되는데 즉, 오브젝트 안에 있는 카운트라는 아이만 변하기 때문에 결국은 동일한 오브젝트이다. habit을 업데이트 하지않고 habit안에 있는 카운트만 증가시켰다.
아래그림은 리셋하는건데
증가하는 것에 가보면 habit안에 있는 카운트만 증가했다.
즉 동일한 habit에서 오브젝트를 쓰면서 카운트만 증가했기 때문에 결국은 shallow comparison은 object에 레퍼런스를 검사한다고 했는데 props 안에 들어 있는 habit을 그대로 유지하면서 안에 있는 데이터가 변해서 동일하다고 판단되어서 render() 함수가 호출 안되는 것이다.
app에서 state를 업데이트 할 때 이 안에 있는 state를 바로 수정하지 못했다.
this.state.habits[index].에 있는 어떤 것을 추가한다 이런것을 바로 못했던 이유가 리액트가 shallow comparison을 이용하기 때문인데, 안에 있는 데이터를 수정 하기만 하면 결국 동일한 오브젝트이기 때문에 같다고 판단해서 업데이트를 하지 않는다. 그래서 전체적인 오브젝트를 업데이트 했다.
해결할 수 있는 방법은 전달할 때 변화하는 것을 따로 빼서 오브젝트로 전달하는 것이다. 즉 habit에서 habit이라는 것을 전달해 주고 카운트가 계속해서 변경되기 때문에 habit에 있는 카운트를 따로 떼서 준다.
habit에 와서 카운트를 따로 가지고 온다. 카운트가 따로 들어오기 때문에 그 부분이 업데이트 된다.
이름도 따로 가지고 오면
이렇게 하면 사용하면 불편한데 habit이라는 컴포넌트는 habit이라는 오브젝트와 굉장히 밀접한 연관이 있는 컴포넌트인데 habit이라는 컴포넌트는 habit이라는 모델 오브젝트에 데이터를 어떻게 ui적으로 표현할 건지 알고 있는 컴포넌트이다. 그래서 이 컴포넌트는 props으로 habit을 받는 것이 더 로직상으로 효율적이다.
오브젝트 전체를 업데이트 하지 않고 오브젝트 안에 있는 데이터를 변경해서 이런 문제가 발생 하는 건데, 이런 멀티스레딩 환경에서 concurrenct(병행성)가 발생하는 이런 환경에서 오브젝트를 변경하는거 데이터 자체를 안에 변경하는 거특히 오브젝트가 다른 오브젝트를 가지고 있고 그 안에 다른 오브젝트가 있고 많은 것들이 연결될 때 작은 단위 안에 들어있는 데이터만 변경하는 것은 좋지 않다.
항상 오브젝트는 가만히 놔두고 데이터가 변경이 되면 다시 새로운 오브젝트를 만드는 게 더 좋다. 그래서 오브젝트가 변경될때마다 새로운 오브젝트를 깊이있게 다 만들면 번거로우니까 이런 상태를 관리하는 라이브러리가 따로 있지만 한번 만들어 보면 app.jsx에서 habit을 돌면서 기존에 있는 아이템을 다른 걸로 바꾸는데 만약에 아이템이 id가 업데이트해야 되는 habit의 id와 동일하면 카운트를 증가해서 새로운 habit을 만들고(새로운 habit을 리턴하고)
새로운 값은 habit에 있는 카운트에 1을 증가한 것으로 만들 것이다. 그리고 id가 동일하지 않다면 기존에 받은 아이템을 리턴한다. 맵은 기존 state.habits을 돌면서 id가 같다면 새로운 habit에서 오브젝트를 만드는데 대신에 카운트만 1 증가하고 아이디가 다르면 업데이트 할 필요가 없어서 기존의 같은 habit 아이템을 이용한다.
Decrement는 바로 1로 가면서 하면 - 값이 나오니까 바로 업데이트 안하고 따로 빼서 카운트 결과 값이, 0보다 작으면 0을 쓰고, 아니면 새롭게 계산한 값 count를 써라고 지정한다.
navbar의 파일도 동일하게 아래와 같이 PureComponent로 바꿔주고(개발자도구 컴포넌트창 확인하면 0인 상태에서 업데이트가 안되는 것을 확인 할 수 있다.)
habits.jsx 파일은 PureComponent로 바꾸지 않아도 된다 왜냐.
habit은 habit 목록을 받아오는데 즉, props.habit을 받아야 되는데 habit이라는 것은 각각의 습관들이 모여있는 리스트 목록 같은 아이다. 그 안에 카운트가 변경되고 어떤 것이 업데이트 되어도 결국은 새로운 habit에 목록을 만들어서 업데이트 하기 때문에 PureComponent를 써도 동일하게 업데이트 되어서 불필요하다. 그래서 그냥 Component를 유지해도 좋다.
결론
끝으로 PureComponent와 오브젝트 비교를 해봤는데 이러한 오브젝트와 shallow와 Deep의 차이점 이런 것을 계속해서 학습하고 이해하는 것이 중요하다. 나중에 리액트로 어플리케이션을 만들거나 버그가 생겨서 디버깅 하는데 한참 시간을 낭비하는 일이 계속 발생할 것이기 때문에,,,
오브젝트 안의 데이터가 변경이 되어도 동일한 오브젝트는 동일한 레퍼런스를 가지고 있어서 그냥 레퍼런스를 비교하는 === 쓰면 true로 나온다.
이러한 shallow comparison이나 실제로 데이터를 깊이 있게 비교하는 차이점에 대해서 비교를 해보자.
<출처 : DreamCoding 리액트 개념 정리+유튜브 클론코딩: ellie>
참고: https://academy.dream-coding.com/courses/react-basic
리액트 강의 (유튜브 클론 코딩 + 실시간 전송 명함 카드 만들기 웹앱 만들기)
리액트 전반적인 개념 설명과 (클래스 컴포넌트와 함수 컴포넌트 그리고 리액트 훅까지) 실전 유튜브 클론 코딩 프로젝트. Firebas의 실시간 데이터베이스를 이용해 멋진 명함 카드 만들기 웹 어
academy.dream-coding.com
'React > HabitTracker' 카테고리의 다른 글
function 컴포넌트, memo 정리 (0) | 2021.08.01 |
---|---|
Lifecycle 함수들 이해 하기 (0) | 2021.08.01 |
About Props in React (0) | 2021.08.01 |
JS Object 가변성에 대해서(Spread Operator) (0) | 2021.07.31 |
PureComponent정리와 차이점 최종 정리 (0) | 2021.07.31 |