[React] 기초에서 벗어나기 : State와 re-rendering에 대해

김영인·2021년 11월 3일
4

기본 : State가 변하면 해당 state가 들어있는 component가 re-rendering된다!
-> 조금 더 이 과정이 어떻게 진행되는지 자세히 알아보자

이 글은 함수형 컴포넌트와 React Hooks을 기반으로 작성하였습니다.

1. state 변경: setState만 사용해야 하는 이유

  • react는 setState를 호출에 의한 state 변경에만 반응하여 해당 컴포넌트가 re-rendering된다.
  • react 배우기 시작한 초기에 state = value 이런 식으로 변경하면 되지 않을까하는 생각을 해볼 수 있지만 react가 해당 변화를 감지하지 못한다는거..

2. state 변경과 re-rendering 타이밍

  • setState가 호출되는 시점? X
  • 해당 코드가 들어있는 함수가 모두 실행된 이후 O

    setState는 비동기적으로 호출된다!
    그러므로 setState와 같은 함수 스코프에서 state를 다룬다면 조심해야 한다.

예시) 버튼을 클릭했을 때 총 클릭한 횟수를 console에 출력해주는 buttonClickHandler 함수가 있다고 가정하자.

  • clickedNumber는 buttonClickHandler 함수가 출력 완료될 때까지는 변화가 없다
  • console.log(clickedNumber)는 클릭한 횟수 아니라 그 전 값으로 출력된다.(BAD)
  • 변경될 것을 예상하고 setState 내부 로직을 그대로 구현하여 콘솔에 출력해야 한다. (GOOD)
  • 이런 점이 불편하다면 useEffect 내부에 console.log(clickedNumber)를 출력하는 코드를 넣자.(물론 deps에 clickedNumber 필요)
const exampleComponent = () => {
	const [clickedNumber, setClickedNumber] = useState(0); // 클릭된 횟수
	const buttonClickHandler = () => {
    	setClickedNumber((prev) => prev+1)
      	// console.log(clickedNumber) // X
      	console.log(clickedNumber + 1) // O
    }; // 이후 Re-rendering
    
    return <button onClick={buttonClickHandler}>Click!</button>
};

3. re-rendering은 DOM(real DOM)을 전부 갈아끼우나? No!

  • 컴포넌트가 real DOM으로 re-rendering이 되기 전에 re-evaluate(재평가)를 거친다.
  • re-evaluate(순서는 읽기 편하도록 나눠놓은 것이다. 실제로 이렇게 단계별로 진행되진 않는다. ㅎㅎ)
    1) 새로운 state를 가지고 해당 컴포넌트(함수) 재실행(re-run)
    2) 그 과정에서 컴포넌트 내부 구조는 모두 재생성(re-creation)(몇몇 hook 제외)
    3) react가 변화 전과 변화 후 virtual DOM(가상 DOM)을 각각 생성하여 비교한 후, 차이점을 ReactDOM에게 전달
    4) ReactDOM이 차이가 있는 부분만 real DOM으로 update(re-render)

re-evaluating the component !== re-rendering the DOM
실제로 전부 바뀌었으면 엄청 느릴 듯 ㅋㅋ

4. State scheduling, Batching : setState 호출 횟수 !== re-rendering 횟수

setState는 비동기적으로 동작한다.

  • state scheduling : 같은 state에 대한 변화가 거의 동시에 일어날 때(예를 들어 한 이벤트에 대한 반응), react는 각각의 변화된 state를 컴포넌트에 바로 반영하는 것이 아니라, scheduled state changes를 순서대로 keep해놓았다가(postpone) 이 변화들을 순차적으로 계산한 후, 컴포넌트에 반영(re-evaluate, re-render)한다.
    - 이 때문에 이전 state에 의존하여 setState를 호출할 경우 이전 state를 인자로 담아주자
setState((prev)=> !prev);
  • state batching : 여러 state의 변화가 거의 동시에 일어날 때, 각각의 state마다 re-rendering을 하는 것이 아니라 동시에 변화된 state를 가지고 한 번에 컴포넌트에 반영한다(re-evaluate, re-render).
const clickHandler = () => {
	setClickedNumber((prev)=> prev + 1);
	setIsClicked((prev) => !prev);
} // re-rendering 1회

5. hook을 이용하여 렌더링 최적화하기

  • useCallback : 함수 re-creation 최소화(렌더링 성능 최적화)
  • useMemo : 값 re-creation 최소화(연산 최적화)
  • 특히 참조 타입일 경우 re-create 될 때마다 참조값이 변경되므로 자식 컴포넌트의 불필요한 렌더링이 발생할 수 있어 위를 적절히 사용해야 함.
  • 자세한 설명은 나보다 대단한 개발자 분의 좋은 글이 있어서 공유!!

    링크 : useReducer, useCallback, useMemo

profile
꾸준히, 그리고 정직하게 성장하는 프론트엔드 개발자

0개의 댓글