리액트의 성능을 개선하기 위해서 최대한 리렌더링을 줄이기 위한 컴포넌트가 pureComponent
이다.
console.log(habit)
을 작성한다.React는 State에 변경이 있다면 리렌더링을 시킨다.
따라서 최상위 컴포넌트에서 정의된 state가 업데이트 되면 모든 자식 컴포넌트들도 업데이트 된다.
콘솔에 확인해보자
초기 렌더링에는 모든 컴포넌트에서 console.log()가 실행된다.
그 후 count 증가 버튼을 클릭했을 때 최상위에 정의된 state에 변경이 있기 때문에 모든 자식 컴포넌트에 적힌 console.log가 실행된다.
두 번씩 나오는 이유는
<React.StrictMode>
를 사용했기 때문이다.
두 번씩 호출했을 때에도 이상이 없는지 확인시켜주는 역할을 한다.
개발하는 과정에서만 발생하기 때문에 배포시에는 나오지 않는다.
📢 리액트는 state가 업데이트 되면 전체를 업데이트 시킨다. 하지만 다행이도 virtual dom이 있기 때문에 업데이트 된 부분만 리렌더링 된다.
즉, 렌더링은 최상위가 수정되면 자식들도 다 리렌더링을 하지만 실제로 업데이트는 바뀐 값만 업데이트 된다!
디버깅을 할 때 개발자 도구에서 요소탭에 깜빡임이 너무 많다면 변화가 너무 많다는 뜻이고 그만큼 업데이트 되는 부분이 많아진다. 성능이 느려지는 것은 아닌데 잘못된 코딩을 하고 있는 것이다.
componentDidUpdate()
라는 함수가 있다.
이는 컴포넌트가 업데이트 될때마다 명령을 실행하라는 함수이다.
componentDidUpdate(){
console.log("컴포넌트 업데이트 되었습니다.")
}
만약 컴포넌트가 업데이트될 때마다 무거운 작업을 해야한다면?
그럼 예상하지 못한 요소 깜빡임이나 불필요한 작업들을 수행할 수 있다는 뜻이다.
실제로 state가 업데이트 되지 않는 곳에서 계속 리렌더링이 일어난다는 것은 성능에 좋지 않다.
어떤 동작을 했을 때 어떤 컴포넌트가 업데이트 되는지 확인하려면 콘솔로그 보다는...
체크박스를 선택하면 업데이트 되는 컴포넌트르 브라우저 화면에서 볼 수 있다.
이런식으로 업데이트 되는 모든 컴포넌트에 테두리가 생긴다.
단지 count 버튼만 눌렀을 뿐인데 거의 모든 컴포넌트가 리렌더링 되고 있다.
즉 pureComponet의 state에 변화가 없다면 render가 일어나지 않는다.
import {PureComponent} from 'react';
class AddHabits extends PureComponent
이렇게 작성한다.
✅ Pure Component를 사용하면
this.props.handleIncrement(this.props.habit)
우리가 수정하고자 하는 값은 habit의 count 값이다.
얕은 복사는 주소값을 참조하기 때문에 habit 안에 count가 바뀌던 말던 전체적인 주소값만 바라봐서 count가 바뀌어도 바뀐줄 모르고 re-render를 하지 않는다.
따라서 + 버튼을 클릭해도 count 수는 증가하지만 UI에 그려지지 않는다. 왜? 데이터가 달라져도 주소값은 같으니 수정된지 모른다. 따라서 업데이트가 일어나지 않는다.
중첩된 객체까지 복사할 수 있는 2가지 방법
1. 객체를 정의한다.
habit = {id: 1, name: name, count: 0}
//위의 state를 props로 넘겨준다고 가정한다.
<자식컴포넌트
habit={this.habit}/>
//위의 방식으로 보통 넘겨주지만 객체 내용이 바뀐 것을 알고 업데이트 하려면
<자식컴포넌트
count={this.state.count}>
🚨 but! 로직상 따로따로 전달하는 방법은 효율적이지 않다. 어차피 habit의 내용이 전체적으로 자식 컴포넌트에 필요한 상황이다.
얕은 복사로 수정할 객체 덮어씌우기
Habit.jsx 가 PureComponent일 때
왜 지정했나?
Habit.jsx에는 클릭하면 count 값이 증가하는데 이를 매핑했기 때문에 Reading 습관만 count 올려도 매핑된 값이기 때문에 coding이나 running 컴포넌트에도 불필요한 rendering이 작용된다. 심지어 값이 수정되지도 않았는데. 따라서 purecomponent 즉, 해당 값이 바뀌면 그 값을 가지고 있는 요소만 리렌더링 하는 것, 반대로 데이터가 업데이트 되지 않은 요소는 불필요한 리렌더링을 하지 않는다.
//원래코드 app.jsx
handleIncrement = (habit) => {
const habits = [...this.state.habits];
const index = habits.indexOf(habit);
habits[index].count++;
this.setState({ habits });
};
//count값이 바뀐 요소만 업데이트 및 리렌더링 하기
handleIncrement = (habit) => {
const habits = this.state.habits.map((item) => {
if (habit.id === item.id) {
return { ...habit, count: habit.count + 1 };
}
return item;
});
this.setState({ habits });
};
결과는
handleReset 함수 또한 똑같이 0이 아닌 count에만 적용하기
//원래코드
handleReset = () => {
const habits = this.state.habits.map((habit) => {
habit.count = 0
return habit;
});
this.setState({ habits });
};
//수정된 코드
handleReset = () => {
const habits = this.state.habits.map((habit) => {
if (habit.count !== 0) {
return { ...habit, count: 0 };
}
return habit;
});
this.setState({ habits });
};
결과는