React의 백그라운드
React는 일단 JS의 라이브러리라는 것을 잊지 말아야 한다
React의 핵심은 컴포넌트
=> React는 컴포넌트를 통해 사용자 인터페이스를 효과적으로 구성하고 업데이트한다
React DOM?
사실 React는 웹에 관심이 없다.
컴포넌트를 어떻게 다루어야 하는지에 대해서는 알고 있지만 웹에 뭘 어떻게 보여주느냐는 별로 관심이 없음
이건 React DOM이 고려할 것들
React는 가상 DOM을 사용한다.
이 가상 DOM은 컴포넌트 트리를 결정한다.
각각 하위 트리를 갖고 있는 컴포넌트들은 JEX 코드를 반환하는데,
가상 DOM은 컴포넌트 트리의 현재 모양과 최종 모양을 결정한다.
=> 상태가 업데이트되면 가상 DOM을 통해 상태 변화를 판단.
React는
- 컴포넌트를 관리하고
- 상태 객체를 관리하고
- 다른 객체의 상태와 컴포넌트가 바뀌어야 하는지를 확인하고
- 컴포넌트 변경 전후의 상태를 확인하는
라이브러리일 뿐.
그리고 React는 화면에 표시되어야하는 모든 정보를 사용중인 인터페이스에 보낸다.
이 때 React DOM은 사용자가 보고 있는 화면에 무엇을 표시할지 판단한다.
정리해서 말하자면
React는 props, state, context를 통해 컴포넌트를 관리한다(핵심 기능만)
그리고 props, 상태 또는 객체가 변경이 되면 컴포넌트도 React를 통해 변경되고
React는 컴포넌트가 화면에 새로운 것을 표시하는지 확인한다.
이렇게 화면에 무언가를 그리려면 React는 React DOM에게 이를 알려
React DOM이 새로운 것을 출력할 수 있게 한다.
하지만 React에 의해 컴포넌트 함수가 재실행된다고 해서 실제 DOM의 각 부분이 다시 렌더링되거나 재평가되는 것은 아니다
컴포넌트 / 리액트 / 실제 DOM을 구분할 수 있어야 한다
컴포넌트는
상태, props, context가 변경될 때 재평가
이러면 리액트는 컴포넌트 함수를 다시 실행시킨다
실데 DOM은 react가 구성한 컴포넌트의 이전 상태와 트리, 현재 상태간의 차이점을 기반으로 변경이 필요할 때만 업데이트된다
즉, 실제 DOM은 필요한 경우에만 반영된다
성능 측면에서 중요
이전과 현재 상태를 가상으로 비교한다는 것은 간편, 자원도 적게 든다
메모리 안에서만 발생하기 때문에
실데 DOM을 이용하는 것은 성능에서 부하가 많이 생긴다.
여러 곳에서 쇼구모 변경이 발생한다면 실제 DOM을 많이 사용하므로 페이지가 느려질 수 있다
=> REACt는 가상 dom과의 비교를 통해 최종 스냅샷과 현재 스냅샷을 실제 DOM에 전달하는 구조를 가진다
=> 이게 REACT 작동 방식.
또 React DOM은 전체 DOM을 재렌더링하지 않는다.
previous evaluation result와 current evaluation result를 비교한 후 변화가 확인된 것을 React DOM에 보고하면
React DOM은 실제 DOM을 업데이트하고 새로운 내용을 집어넣는다
다른 요소는 건드리지 않고 변화한 내용만 추가.
이게 background에서 react가 작동하는 방식.
실습
props는 부모 -> 자식이다. 그런데 이 값이 언제 바뀔까?
부모의 값이 다른 상태로 바뀌었을 때 변한다.setWhat(prev => !prev)
를 쓰는 이유
상태변경, props변경, context 변경은 component 함수를 다시 실행시킨다.
매 번 상태 변경이 발생하면 컴포넌트 전체가 재실행되고, 평가된다
실제 DOM에는 어떤 영향을 줄까?
DOM에서 발생한 변경분을 강조표시해준다 => 재랜더링된 부분을 알 수 있음
실제 DOM을 통한 업데이트는 가상 스냅샷 간의 차이점만 반영되었다.
** 이게 진짜 중요
그런데 만약, 부모에서 자식 component로 값을 전달하지 않고, 부모 state만 계속 바뀌는 경우에는 어떻게 될까?
자식 component도 다시 랜더링될까?
자식 component도 또 랜더링된다
이유 : 부모 component에 있는 JSX 요소들은
결국 컴포넌트 함수에 대한 함수 호출과 같다.
따라서 자식 함수도 재실생, 재평가된다.
자식 컴포넌트는 부모 컴포넌트의 일부이기 때문에.
따라서 props값은 여기서 상관없다.
부모가 바뀌면 그냥 바뀌어버리니까
** 컴포넌트의 재평가와 재실행이 일어나도 Real DOM이 변경되는 것은 아니다.
컴포넌트가 재실행되면, 모든 자식 컴포넌트도 재실행, 재평가된다.
그 자식 컴포넌트도 당연히 재평가.
엥..?
이거.. 괜찮나..?
엄청 많은 함수들의 가상 비교가 이루어진다는 말인데.. 성능에 괜찮을까?
react.memo
이 때 react.memo가 필요하다
이는 함수형 컴포넌트에만 가능하다.
memo의 역할
인자로 들어간 컴포넌트에 어떤 props가 입력되는지 확인하고
입력되는 props값과 비교하고, 값이 바뀐 경우에만 컴포넌트를 재실행,재평가
=> 값이 바뀌지 않았을 경우 그냥 내보낸다.
이렇게 최적화가 이루어진다
그런데 이렇게 최적화가 가능하면, 왜 모든 component에 적용하지 않나?
memo는 변경이 발생할 때마다 기존 props와 새로운 값을 비교하게 된다
그러면 react는
- 기존 props를 저장할 공간과
- 비교하는 작업이 필요하다
따라서 효율이 낮아질 수 있음,
어떤 component를 최적화하냐에 따라서 효율이 달라질 수 있음.
이 때 기준이 되는 것이 - props의 개수
- component의 복잡도
- 자식 컴포넌트의 숫자
에 따라 달라진다.
자식 컴포넌트가 많아 컴포넌트 트리의 상위에 위치해있고 트리가 엄청 크다면 효율적일 수 있다.
엥?
그런데 가끔, props 값이 불변임에도 재랜더링이 되는 컴포넌트가 있다
react의 흔한 오류.
함수 자체가 새로 만들어지면서 모든 실행 사이클에서 완전히 새로운 함수로 인식되기 때문에
= 같은 함수가 아니라 같은 기능을 하는 새로운 함수로 인식됨
원인
React.memo는 props의 값을 확인하고 props.show를 직전의 값인 props.previous.show와 비교(이런 식)
이 작업은 일반 비교 연산자를 통해서 한다.
따라서 값이 원시값
이라면 두 개의 문자열 / boolean / 숫자를 비교하면 같은 값이 나온다.
하지만 배열 / 객체 / 함수를 비교하면 말이 달라짐
자바스크립트에서 [1,2,3] === [1,2,3,]
은 다르다
자바스크립트에서는 함수는 하나의 객체에 불과하다
따라서 항상 새로운 함수 객체가 전달이 되는 것
=> 그래서 React.memo는 항상 새로운 값이 생성되었다고 생각하게 된다.
해결책
객체를 생성하고 저장하는 방법을 변경해주면 된다.
React에서 제공해주는 useCallback Hook
=> 컴포넌트 실행 전반에 걸쳐 함수를 저장할 수 있다
따라서 매번 실행할 때마다 이 함수를 재 생성하지 않아도 된다고 말해줄 수 있다.
정리하자면
useCalllback은 함수를 React의 내부 저장 공간에 저장해서 함수 객체가 실행될 때마다 이를 재사용할 수 있게 한다
사용법
useCallback 안에 함수를 넣어주면 된다
두번째 배열은 함수를 감싼 컴포넌트로부터 전달받는 모든 것을 사용할 수 있다.
const a = useCallback(()=>{
fc((prev)=> !prev)
},[])
강의 소스
【한글자막】 React 완벽 가이드 with Redux, Next.js, TypeScript
'✍ 공부 > React' 카테고리의 다른 글
함수형 컴포넌트로 ref 보내기 | forwardRef에 대해 알아보자 (0) | 2022.06.12 |
---|---|
[ 🐱💻 강의 노트 ] React 완벽 가이드 강의 정리 섹션 10 | 리듀서(Reducer)을 사용하여 부작용 처리 & 컨텍스트 API(Context) 사용하기 (0) | 2022.06.12 |
React 프로젝트 구조 잡기 (0) | 2022.06.08 |
[ 🐱💻 강의 노트 ] React 완벽 가이드 강의 정리 섹션 27 | React + TypeScript 사용하기 (0) | 2022.06.01 |
[ 🐱💻 강의 노트 ] React 완벽 가이드 강의 정리 섹션 29 | 리엑트 요약 및 핵심 기능 (0) | 2022.05.31 |